miércoles, 23 de noviembre de 2011

Apache Tapestry 5.3 publicado (Notas de publicación)

Java
La versión 5.3 de del framework Apache Tapestry para el desarrollo de aplicaciones web dinámicas en Java ha sido publicada y así ha sido anunciada en la lista de correo. Continúa con la línea de las series 5.x e incorpora muchas novedades, mejoras y correcciones de errores, casi 350 peticiones han sido resueltas. Varias de estas nuevas características se suman a algunos Motivos para elegir el framework Apache Tapestry. Las lista de novedades es la siguiente:
Apache Tapestry

Documentación
La ya buena documentación que existía ha sido mejorada y ampliada con nuevas secciones. Así tenemos un tutotial con el que los usuarios que estén empezando les resulte más fácil la tarea, incluye las herramientas a utilizar, las dependencias, como emepezar una aplicación rápidamente utilizando el archetype de maven y un par de ejemplos sencillos para empezar a hacer algo asi como usar Tapestry con Hibernate. También se ha añadido una sección de recetas sobre como hacer cosas habituales en muchas aplicaciones. También se ha añadido una sección de preguntas frecuentes y unas páginas de consulta para características de uso habitual (Application Module Class Cheat SheetComponent Cheat Sheet).


Alertas
Tapestry ahora tiene un mecanismo central para manejar las alertas de usuario. Esto incluye el servicio AlertManager y el componente Alerts. Simplmente añade el componente Alerts a tu layout estandar y Tapestry se encarga del resto. Las alertas se pueden añadir tanto en las peticiones tradicionales como en las peticiones Ajax y pueden ser transitorias (se muestran durante unos segundos), normales o fijas (se mantienen entre peticiones hasta que el usuario las elimina expresamente haciendo clic). Las alertas pueden ser de tres severidades: info, advertencia (warn) y error. La apariencia puede ser personalizada sobreescribiendo las reglas CSS de Tapestry.

Salida de comentarios
Ahora es posible que Tapestry emita comentarios de salida. Esto son comentarios (como <!--BEGIN Index:loop (context:Index.tml, line 15)-->) que ayudan a depurar las etiquetas de salida en la parte del cliente. Esto se habilita para todas las peticiones que usen el símbolo de configuración «tapestry.component-render-tracing-enabled» y pueden ser añadidas a cualquier petición añadiendo el parámetro «t:component-trace=true» a la URL. Esto aumenta significativamente el tamaño de las etiquetas de salida pero puede ser muy útil con layouts compejos para determinar que componente es el responsable de cada porción de la página.

Contribuciones de servicio adaptables
Al hacer contribuciones a un servicio ahora no se está limitado a contribuir un valor que sea asignable al tipo asociado con la configuración. Objetos de cualquier tipo pueden ser contribuidos y el servicio TypeCoercer será usado para transformar el valor al tipo de la configuración (Ver documentación TypeCoercer).

Mejoras en la depuración de componentes
Dada la forma en como Tapestry instrumentaba las páginas y componentes usar un depurador ha sido dificultoso con las clases de páginas y componentes, cualquier campo mutable muestra su valor por defecto en el depurador independientemente de que haya sido escrito en el campo o leído de él. En el modo desarrollo de Tapestry 5.3 los valores ocultos leen de y escriben a esos campos en los propios campos (esto también ha sido corregido en la rama de mantenimiento 5.2.5). Este ocultamiento no ocurre en el modo producción para evitar potenciales pérdidas de memorias.

Recarga deshabilitada en producción
Desde ahora no se buscan cambios en los archivos de clases, plantillas o catálogos de mensajes en el modo producción. Es asumido que las aplicaciones Tapestry son empaquetadas en archivos WAR en producción y que cambiar el archivo WAR causa que el contenedor de servlet redespliegue la aplicación entera. Este cambio mejora el rendimiento y reduce el consumo de memoria en las aplicaciones.

JavaDoc Tapestry
Tapestry ahora incluye una nueva librería , tapestry-javadoc, que reemplaza el antiguo componente de informes basado en Maven. Simplemente poniendo una anotación @tapestrydoc en el JavaDoc de los componentes Tapestry generará la documentación completa como parte del JavaDoc... no mas cambios entre el JavaDoc y el informe del componente generado por Maven y no mas dependencia en Maven para la documentación de los componentes. Los estilos del JavaDoc han sido adaptados a los colores del proyecto.


Template skinning
Tapestry añade la posibilidad de personalizar y/o tematizar («skin and/or theme») las páginas o componentes. Es una extensión de como Tapestry gestiona las páginas por locale pero añade ejes definidos por aplicación además de reglas para encontrar recursos. Ver ComponentResourceSelector y ComponentRequestSelectorAnalyzer.

También hay un nuevo componente Dynamic el cual usa una plantilla externa (no una plantilla de Tapestry) que puede ser seleccionada en tiempo de ejecución.

Mediante esta funcionalidad ahora la plantilla que se aplica para una página o componentes no solo vendrá determinado por el locale de la aplicación sino que podremos introducir más variables en la elección. Una de ellas podría ser el tipo de cliente, como por ejemplo un dispositivo móvil del que hace la petición u otros criterios para asi mostrar contenido más adaptado o personalizado.

En el siguiente enlace de puede ver una demostración de como aplicar la funcionalidad Template Skinning.

Evento de formulario cancelado
Los componentes de formulario ahora reconocen cuando el formulario de la parte cliente fue cancelado. Un nuevo evento «cancelled» es disparado al principio del proceso de envío lo cual permite a la página saltar todas las actualizaciones y validaciones en el servidor cuando sea lo deseado.

Restricciones implicitas en OrderedConfiguration
Al usar OrderedConfiguration.add() sin restricciones Tapestry ahora implicitamente ordenará el elmento añadido después del elemento añadido anteriormente dentro del mismo método. En versiones anteriores esos elementos eran añadidos sin restricciones. Esto hace más fácil contribuir un grupo de elementos relacionados con un orden implicito.

Nuevas validaciones de las clases de componente
Tapestry incluye nuevas validaciones de clases de componente para ayudar a reducir algunos errores comunes. Ahora comprueba que los componentes referenciados por un manejador de eventos concuerde con un componente definido en la plantilla... esto identifica rápidamente errores tipográficos en los nombres de métodos. Esto es, si la firma de un manejador de evento es «onFormularioRegistroSubmit()» se comprueba que exista en la plantilla un componente de id «FormularioRegistro». Esta comprobación puede ser deshabilitada con un símbolo de configuración de tal modo que aplicaciones 5.2 existentes que tengan este tipo de errores todavía puedan funcionar (esto es, tendrá manejadores de eventos muertos que nunca serán invocados).

Carpeta de aplicación
Tapestry ahora puede ser configurado para ejecutarse dentro de una carpeta, lo cual puede ser útil cuando se ejecuta dentro de una aplicación web que contiene otros servlets y filtros como una forma de prevenir conflictos. En vez de ejecutarse en la raiz del contexto de la aplicación.

Mejorado el arquetipo de inicio rápido
El arquetipo de Maven ha sido actualizado y ahora muestra varias nuevas funcionalidades de uso común. Además añade soporte para Gradle (Referencia). La forma de usarlo desde la linea de comandos es:

$MAVEN_HOME/bin/mvn archetype:generate -DarchetypeCatalog=http://tapestry.apache.org/

Páginas blancas
La nueva anotación @WhitelistAccessOnly marca una página como accesible solo por una lista blanca de clientes. Las reglas para la lista son extensibles. La regla por defecto de la lista blanca es que sea accedida desde 127.0.0.1. Usa esta anotación en páginas que pueden exponer datos sensibles como paneles informativos incorporados.

Catálogo de página
Las aplicacicones Tapestry incluyen una página PageCatalog la cual lista todas las páginas cargadas de la aplicación con detalles acerca de su tiempo de construcción y número de componentes. La página requiere acceso en la lista blanca (ver en comentario anterior). Muestra alguna información que solo está disponible en el modo desarrollo. PageCatalog puede ser usada para cargar todas la páginas de la aplicación lo cual es útil para localizar problemas en las páginas... y es especialmente útil al aumentar de una versión anterior de Tapestry.

Otra página de la que podemos obtener información acerca de los servicios de la aplicación es ServiceStatus donde podremos ver el estado de los mismos.





Logging en la parte ciente JavaScript
La consola en la parte cliente Blackbird ha sido eliminada. En su lugar están los mensajes de la consola flotante combinada con el logging a la consola de Firebug o WebKit. Tapestry no captura más las excepciones de JavaScript de inicialización de modo que pueden ser informadas apropiadamente en la consola nativa. Estos cambios deberían hacer la depuración de JavaScript en el lado cliente mucho más fácil.

El informe de error es una de las cosas buenas de Tapestry. Con esta nueva versión esta característica se ha extendido a las peticiones Ajax con lo que cuando se produzca una excepción en el servidor se nos mostrará un informe en una ventana emergente con un contenido similar a las excepciones de las peticiones normales. En este screencast se puede ver en funcionamiento.


Otra lista de novedades no mencionadas en la notas de lanzamiento oficial:

Nuevos componentes
Se han añadido unos cuantos nuevos componentes al los ya presentes en el core. Estos son:
  • KaptchaImage / KaptchaField: Permite evitar el spam en los formularios.
  • Tree: componente ábol que pemite mostrar información jerárquica con nodos expandibles, contraibles y seleccionables. Es personalizable y basado en Ajax.
  • Checklist: componente de selección multiple consistente en diferentes checkbox.




Nueva capa de abstracción JavaScript
En estos momentos Tapestry está atado a Prototype y Scriptaculous por compatibilidad con anteriores versiones (aunque se puede utilizar perfectamente junto con otros frameworks javascript como jQuery). En un futuro la nueva capa capa de abstracción permitirá usar cualquier librería JavaScript. La transición se completará en la versión 5.4.

Soporte Ajax mejorado
La clase MiltiZoneUpdate ha sido marcada como obsoleta en favor del servicio AjaxResponseRenderer. Esta nueva clase permite actualizar una zona con el contenido de Block, Component u otro objeto capaz de ser convertido a RenderCommand. Permite además importar una librería JavaScript como parte de la respuesta Ajax y añadir una llamada a una función en el lado del cliente, útil y a veces necesario para procesar la petición Ajax correctamente.

Integración con JSR 330 (Dependency Injection for Java)
Empezando desde Tapestry 5.3 es posible usar las anotaciones que define la especificación JSR-330 en la capa de servicios de tal modo que no dependan de Tapestry.

Integración JPA2
Integración nativa con JPA2 proporcionando compatibilidad hacia atrás en futuras versiones y posibilidad de usar múltiples unidades de persistencia en la misma aplicación. Permite configurar JPA sin necesidad de XML.

Herramienta construcción, Gradle
Tapestry ha pasado de utilizar Maven a usar Gradle como herramienta de contrucción y compilación para el propio proyecto. Gradle aporta nuevas funcionalidades, simplifica otras y evita algunos de los aspectos menos deseables de Maven.

Actualización Jumpstart
Actualizada la aplicación de referencia Jumpstart a la versión 5.3 con numerosos ejemplos que muestran funcionalidades, y con la posibilidad de ver el código fuente de esos ejemplos.


Otras
  • Incorporación de Underscore.js que añade programación funcional a JavaScript (en modo no conflicto).
  • Coercion de String-a-Enum sin necesidad de hacer una contribución en el contenedor IOC.
  • Compresión JavaScript y CSS a través de de la librería YUICompressor library.
  • Más rápido y más eficiente en uso de memoria.



Cambios disruptivos
  • Tapestry depende ahora del Servlet API versión 2.5 con lo que la versión mínima para ejecutarlo ahora será Tomcat 6.0+.
  • Actualizado Prototype a la versión 1.7.
  • Las plantillas sin son tratadas como si tuvieran el doctype de HTML5 () pudiendose usar entidades como © o &nbsp; sin ver errores de parseo de anteriores versiones.
Algunas interfaces y APIS en Tapstry 5.3 serán eliminadas en Tapestry 5.4 o posteriores. Estas incluyen:
  • Entorno RenderSupport (reemplazada con el entorno JavaScriptSupport).
  • El objeto MultiZoneUpdate, reemplazada con AjaxResponseRenderer.
  • El servicio ClassFactory y la interfaz ClassFab (reemplazada con el servicio PlasticProxyFactory y la interfaz PlasticClass).
  • La funcionalidad "suppress redirects", que permitía a los eventos de los componentes responder directamente con HTML, como en Tapestry 4.
Salvo que se usen características marcadas como obsoletas en Tapestry 5.2 en la nueva versión la actualización no requerirá más que actualizar a las nuevas dependencias.

Y una lista larga de corrección de errores y mejoras. La lista completa está en las notas de publicación de tapestry 5.3 oficiales y aquí una lista de novedades en inglés.

Referencia:
http://tapestry.apache.org/index.html
http://tapestry.apache.org/download.html
Documentación sobre Apache Tapestry
Motivos para elegir el framework Apache Tapestry

viernes, 18 de noviembre de 2011

Personalizar GNOME (iconos, temas, extensiones, opciones)

GNOME
Arch Linux
Una vez que tenemos instalado nuestro sistema una de las primeras cosa que solemos hacer es personalizarlo cambiando el fondo de pantalla, instalando un pack de iconos o cambiando el tema de las ventanas. Aunque GNOME con su versión 3.0 o 3.2 ha perdido algunas funcionalidades y algunas posibilidadesd de configuración la herramienta gnome-tweak-tool permite configurar las opciones más habituales que querrán la mayoría de los usuarios. Para instalarla en arch linux:

# pacman -S gnome-tweak-tool

Una vez instalada veremos su icono en el menu Aplicaciones > Accesorios > Configuración avanzada.

En la pestaña Escritorio podemos configurar algunos comportamientos como mostrar la visibilidad de los iconos Computadora, Papelera, Carpeta Home.


En Extensiones de GNOME-Shell podemos instalar nuevas extensiones que descarguemos aunque es mejor opción hacerlo con el gestor de paquetes de nuestra distribución. Hay una lista de extensiones disponibles para Arch linux que pueden instalarse individualmente y que modifican el comportamiento por defecto del shell de gnome.


En GNOME-Shell podemos personalizar la información que se muestra en el reloj, las acciones a realizar al cerrar la tapa del portátil y los botones que queremos en las ventanas.


En Tema podemos configurar los temas del puntero del ratón, iconos, tema de los componentes GTK+ y de las ventanas. Uno de los temas de iconos más conocidos es el tema faenza disponible en Arch linux en el repositorio AUR con el nombre de paquete faenza-icon-theme. Aunque temas de iconos hay muchos.


En Tipografías se pueden cambiar las fuentes y tamaños de la fuente usada en el sistema, de los documentos, en el título de la ventana y alguna otra opción.


En la opción Ventanas se pueden modificar algunos comportamientos al hacer clic en las ventanas.


La falta de configuración de la versión 3 de gnome es una de las críticas que se le ha hecho pero a medida que vaya evolucionando en versiones lo hará al mismo tiempo en funcionalidades y opciones de configuración, es cuestión de tiempo.

Yo no soy de tener una personalización extrema del escritorio prácticamente tengo con la configuración por defecto exceptuando el tema de iconos faenza.



viernes, 11 de noviembre de 2011

Internacionalización (i18n) de campos con Hibernate

Java
Si estamos desarrollando un sitio web que soporta varios idiomas necesitaremos internacionalizar (i18n) los literales que aparecen en él. Esto incluye también tenerlo en cuenta y solucionarlo en los nombres, descripciones y textos que guardamos en la base de datos de las entidades de dominio y que puedan aparecer en en el html generado.

Hibernate
La primera solución que se nos ocurre para las entidades de dominio es crear un campo por cada idioma y concepto a internacionalizar. Sin embargo, esto tiene el problema de que en la base de datos el número de campos crecerá rápidamente y si tenemos muchos elementos a internacionalizar y muchos idiomas el número de campos puede ser un problema por el tamaño de fila, y aunque los limites en postgresql y los límites de mysql son elevados y más que suficientes para la mayoría de los casos, con la solución anterior podemos llegar a ellos o estar peligrosamente cerca.


Diagrama entidad relación de base de datos
Primera solución (variación número columnas)

Para evitarnos problemas las buenas prácticas de diseño de las bases de datos dicen que las tablas han de estar normalizadas y siguiengo la solución anterior tendríamos variaciones en el número de columnas con lo que inclumpliríamos la primera forma normal (1FN). ¿Pero porque inclumplir esta forma normal puede ser un problema? Porque en el momento que tengamos que soportar un nuevo idioma deberemos modificar el esquema de la base de datos añadiendo un campo por cada concepto de las entidades que haya que internacionalizar. Y hacer esto en una base de datos que está en producción puede ser una fuente de problemas y un peligro. Además, en tablas con muchos registros (de unos cuantos miles o cientos de miles) añadir una columna puede ser muy lento pudiendo llegar a horas o más tiempo cosa que dependiendo del proyecto no es viable al poder tener que realizar paradas largas por mantenimiento.

La solución que voy a explicar a continuación es crear una tabla Diccionario la cual relacionaremos con las tablas de los datos de dominio (Producto) y una tabla Traduccion que relacionaremos con la tabla Diccionario y que contendrá las traducciones para cada idioma. La tabla Traduccion contendrá tres campos la clave primaria del diccionario, el idioma de la traduccion y el literal de la traducción propiamente dicho con lo que ya no tendremos variaciones en el número columnas. En la tabla Traduccion la clave primaria estará formada por la clave del diccionario y el campo locale. El esquema que tendríamos sería el siguiente:

Diagrama entidad relación de base de datos.
Producto-Diccionario 1:1
Diccionario-Traduccion 1:N
 A primera vista la tabla Diccionario no tiene mucho sentido pero nos permitirá crear en Hibernate una clase donde podremos incluir algunos métodos de utilidad de forma que podamos acceder a las traducciones más cómodamente. Para modelar este esquema con hibernate necesitaremos las siguientes entidades de dominio, la entidad Producto, Diccionario, Traduccion y TraduccionPK (necesaria en Hibernate al tener Traduccion una clave compuesta por dos campos o columnas).

// Producto.java (Código resumido)
...

@Entity
@Table(name = "Producto")
public class Producto implements Serializable {
 ...

 @OneToOne(cascade = CascadeType.ALL)
 @JoinColumn(name = "nombre_id", nullable = true)
 private Diccionario nombre;

 @OneToOne(cascade = CascadeType.ALL)
 @JoinColumn(name = "descripcion_id", nullable = true)
 private Diccionario descripcion;

 ...
}

// Diccionario.java
package com.blogspot.elblogdepicodev.domain;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.apache.commons.lang3.StringUtils;

import com.blogspot.elblogdepicodev.misc.AppThreadLocal;

@Entity
@Table(name = "diccionario")
public class Diccionario implements Serializable {

 private static final long serialVersionUID = -1210174827995726573L;
 
 private static final int LONGITUD_TEXTO_ABREVIADO = 10;
 
 @Id
 @GeneratedValue
 private Long id;

 @OneToMany(cascade = CascadeType.ALL)
 @JoinColumn(name = "diccionario_id")
 @MapKeyColumn(name = "locale")
 private Map<String, Traduccion> traducciones;
 
 public Diccionario() {
 }
 
 public Long getId() {
  return id;
 }

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

 public Map<String, Traduccion> getTraducciones() {
  if (traducciones == null) {
   traducciones = new HashMap<String, Traduccion>();
  }
  return traducciones;
 }

 public void setTraducciones(Map<String, Traduccion> textos) {
  this.traducciones = textos;
 }

 //
 public String getTexto() {
  return getTexto(getLocale());
 }

 public void setTexto(String texto) {
  setTexto(getLocale(), texto);
 }
 
 public String getTexto(Locale locale) {
  Traduccion t = getTraducciones().get(locale.toString());
  if (t == null) {
   return null;
  }
  return t.getTexto();
 }

 public void setTexto(Locale locale, String texto) {
  Traduccion t = getTraducciones().get(locale.toString());
  if (t == null) {
   t = new Traduccion(this, locale.toString());
   getTraducciones().put(locale.toString(), t);
  }
  t.setTexto(texto);
 }

 //
 public boolean equals(Diccionario d) {
  if (d == null) {
   return false;
  }
  return getId().equals(d.getId());
 }

 @Override
 @SuppressWarnings({ "unchecked", "rawtypes" })
 public String toString() {
  Map m = new HashMap();
  m.put("id", getId());
  m.put("texto", StringUtils.abbreviate(getTexto(), LONGITUD_TEXTO_ABREVIADO));
  return m.toString();
 }
    
 private Locale getLocale() {
  return AppThreadLocal.getPreferencias().getLocale();
 }
}

// Traduccion.java
package com.blogspot.elblogdepicodev.domain;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import org.apache.commons.lang3.StringUtils;

import com.blogspot.elblogdepicodev.misc.Utilidades;

@Entity
@Table(name = "traduccion")
public class Traduccion implements Serializable {

 private static final long serialVersionUID = -1210174827995726573L;
 
 private static final int LONGITUD_TEXTO_ABREVIADO = 10;

 @Id
 private TraduccionPK id;

 @Column(length = 65536)
 private String texto;

 public Traduccion() {  
 }
 
 public Traduccion(Diccionario diccionario, String locale) {
  this.id = new TraduccionPK(diccionario, locale);
 }
 
 public TraduccionPK getId() {
  return id;
 }

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

 public String getTexto() {
  return texto;
 }

 public void setTexto(String texto) {
  this.texto = texto;
 }
 
 //
 public Locale getLocale() {
  return Utilidades.getLocale(id.getLocale());
 }

 //
 public boolean equals(Traduccion t) {
  if (t == null) {
   return false;
  }
  return getId().equals(t.getId());
 }

 @Override
 @SuppressWarnings({ "unchecked", "rawtypes" })
 public String toString() {
  Map m = new HashMap();
  m.put("id", getId());
  m.put("locale", getId().getLocale());
  m.put("texto", StringUtils.abbreviate(getTexto(), LONGITUD_TEXTO_ABREVIADO));
  return m.toString();
 }
}

// TraduccionPK.java
package com.blogspot.elblogdepicodev.domain;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.Basic;
import javax.persistence.Embeddable;
import javax.persistence.ManyToOne;

@Embeddable
public class TraduccionPK implements Serializable {
 
 private static final long serialVersionUID = 4445136951104395835L;

 @ManyToOne
 private Diccionario diccionario;

 @Basic
 private String locale;

 public TraduccionPK() {  
 }
 
 public TraduccionPK(Diccionario diccionario, String locale) {
  this.diccionario = diccionario;
  this.locale = locale;
 }
 
 public Diccionario getDiccionario() {
  return diccionario;
 }

 public void setDiccionario(Diccionario diccionario) {
  this.diccionario = diccionario;
 }

 public String getLocale() {
  return locale;
 }

 public void setLocale(String locale) {
  this.locale = locale;
 }

 //
 public boolean equals(TraduccionPK t) {
  if (t == null) {
   return false;
  }
  return diccionario.equals(t.getDiccionario()) && getLocale().equals(t.getLocale());
 }

 @Override
 @SuppressWarnings({ "unchecked", "rawtypes" })
 public String toString() {
  Map m = new HashMap();
  m.put("dicionario", getDiccionario().toString());
  m.put("locale", getLocale());
  return m.toString();
 }
}

// Utilidades.java
public class Utilidades {
 ...

 public static Locale getLocale(String locale) {
  String[] s = locale.split("_");
  switch (s.length) {
  case 1: {
   return new Locale(s[0]);
  }
  case 2: {
   return new Locale(s[0], s[1]);
  }
  case 3: {
   return new Locale(s[0], s[1], s[2]);
  }
  default: {
   throw new IllegalArgumentException();
  }
  } 
 }

 ...
}

Esta solución carga las traducciones para todos los idiomas de un diccionario cuando posiblemente solo necesitemos la traducción de un idioma en concreto, tal vez si no quisiésemos que se carguen todos los textos de las traducciones podríamos sacar el campo texto a otra tabla y relacionarla con la Traduccion con un id con lo que los campos de tablas nos quedarían: Traduccion (diccionario_id, locale, texto_id), Texto (id, texto). Aunque con esta última solución necesitamos lanzar una sql más por cada texto que queramos acceder.

Otro problema común que se nos suele presentar en las entidades de dominio es como hacer búsquedas de texto completo («full text seach») en algunas propiedades de esas entidades y con esto no me refiero al débil like de SQL, en el enlace anterior doy varias soluciones (Hibernate Search, SQL, elasticsearch).

Referencia:
http://www.hibernate.org/
http://www.postgresql.org/about/
http://dev.mysql.com/doc/refman/5.0/en/column-count-limit.html
http://es.wikipedia.org/wiki/Normalizaci%C3%B3n_de_bases_de_datos
http://support.microsoft.com/kb/283878/es

viernes, 4 de noviembre de 2011

Formatear precios con símbolo de moneda en Java

Java
Si estamos desarrollando una aplicación que muestre precios y trabaje con diferentes monedas o tengamos que mostrar el precio junto con la moneda de ese precio necesitaremos un NumberFormat configurado para que nos muestre además del precio el símbolo de la moneda. Esto nos será de mucha utilidad en aplicaciones de comercio electrónico, por ejemplo, ya que evitaremos hacer concatenaciones de cadenas String y el código será más legible y fácil de mantener.

Locale locale = ...;
String simbolo = ...;

DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(locale);
dfs.setCurrencySymbol(simbolo);
String pattern = (Constantes.DIVISAS_SIMBOLO_FINAL.indexOf(simbolo) != -1) ? "#,##0.## ¤" : "¤ #,##0.##";
DecimalFormat df = new DecimalFormat(pattern, dfs);

El truco está en hacer uso de un DecimalFormatSymbols y un «¤» en el patrón de formateo. Otra cosa que también tendremos que tener en cuenta si para la moneda que vamos a mostrar el precio el símbolo se pone antes o después de él según la convención de esa moneda. Una vez tenemos el formateador de precio con el símbolo lo usamos de la siguiente forma:

float importe = 13.19;
df.format(importe);

Y obtendremos «13,19 €».

La lógica de obtener el DecimalFormat estaría muy bien tenerlo en una clase de utilidad que lo construya para poder usarlo de diferentes sitios evitando duplicar código.