Performance: Porqué usar @Factory en vez de getters

Cuando JSF resuelve una #{EL Expression} no cachea los resultados. Esto quiere decir que por cada expresión como #{user.name}, se hará una llamada al método User.getName().

Esto por sí mismo no es peligroso. Pero analicemos este caso típico

@Name("userManager")
public class UserManager {

    public List getUserList() {
        return createUserList();
    }
}

<h :dataTable value="#{userManager.userList}" var="user">
    ...
    #{user.name}
    ...
</h>

En este caso por cada fila de la tabla, se hará una llamada al método UserManager.getUserList() para obtener el usuario actual. Si el método UserManager.getUserList() implica una llamada a la base de datos, el tiempo de carga crece imcreíblemente.

Una primera solución es usar el patrón de diseño Carga Perezosa (LazyLoad).

@Name("userManager")
public class UserManager {
    private List list;

    public List getUserList() {
        if (this.list == null) {
            this.list = createUserList();
        }
        return list;
    }
}

De esta forma la lista no se crea cada vez que se la llama desde la página JSF. Pero aún así se sigue invocando el método getUserList() multitud de veces.

La mejor solución es poner la lista en el contexto de la página, y acceder a ella directamente por su nombre en el contexto en vez de por el componente al que pertenece.
Esto se puede hacer mediante la anotación @Out o la anotación @Factory

public class UserManager {

    @Factory(value = "userList", scope = ScopeType.PAGE)
    public List getUserList() {
        if (this.list == null) {
            this.list = createUserList();
        }
        return list;
    }
}

<h :dataTable value="#{userList}" var="user"> // invocamos la variable userList directamente
    ...
    #{user.name}
    ...
</h>

Leave a Comment

Cómo obtener un componente de seam sin usar la anotacion @In

En seam, la forma más fácil de obtener un componente es usando la inyección de dependencias mediante la anotación @In. Sin embargo, las anotaciones para la bijection solo funcionan si se utilizan desde otro componente de Seam marcado mediante la anotación @Name.

Es decir, si en una clase 'normal' (sin la anotación @Name) utilizas un @In o un @Out o cualquier otra anotación propia de Seam, esta será ignorada. Aun así,  la API de Seam nos permite obtener instancias de otros componentes directamente, buscándolos a través de su nombre o de su clase.

// Obtiene una instancia de un componente a través de su clase
Component.getInstance(Class clazz)

// Obtiene una instancia de un componente a través de su nombre
Component.getInstance(String name)

El metodo getInstance también admite otros parametros para indicar si hay que crear el elemento, su contexto, etc..

Leave a Comment

Cambiar el usuario y password de subversion en eclipse

Si estas usando eclipse con el plugin de subversion subclipse, el usuario y el password con el que te conectas se queda almacenado, y el plugin no te da opciones para cambiarlo.

Tampoco sirve de nada eliminar el repositorio (repository location) y volverlo a crear, porque el usuario y password se quedan almacenados en un archivo del disco y están asociados a la url del repositorio.

La solución consiste en eliminar/modificar el archivo desde el que el plugin  lee los usuarios. Este archivo es distinto según el cliente del svn interface que estés usando (mirar en Window - Preferences - Team  - SVN : SVN Interface).

Si usas el cliente JavaHL el archivo esta en una de las subcarpetas de  ~/.subversion/auth  (probablemente ~/.subversion/auth/svn.simple), y hay un archivo de texto plano para cada repositorio svn.

Si usas el cliente SVNKit, eclipse usa el keyring general, que está en $ECLIPSE_HOME/configuration/.keyring. Lo malo que este archivo no está en texto plano y hay que eliminarlo. La próxima vez que te intentes conectar, te pedirá el usuario y el password.

Probablemente tengas que eliminar el repository location desde el plugin (Discard location) y reiniciar el eclipse tras modificar el archivo para que tenga efecto

Leave a Comment

Eventos en Seam

Durante su funcionamiento, Seam lanza una serie de eventos predefinidos al realizar ciertas.
Estos eventos pueden ser escuchados por nuestra aplicación y actuar en consecuencia.
Algunos eventos interesantes son:

  • org.jboss.seam.validationFailed : Se lanza cuando hay un fallo de validación
  • org.jboss.seam.postCreate.<name> : Se lanza cuando el componente con nombre <name> se instancia. Se puede usar de forma similar a la anotación @Create de los EJBs
  • org.jboss.seam.exceptionNotHandled : Se lanza cuando salta una excepción que no es manejada por Seam.
  • org.jboss.seam.beforePhase y org.jboss.seam.afterPhase : Se lanzan respectivamente antes del inicio y después del final de cada fase JSF. Este evento es lanzado con el parámetro PhaseEvent, que indica la fase que empieza/acaba

La lista completa de eventos puede verse en el manual

Para escuchar eventos en Seam debemos usar la anotación @Observe para anotar un método que se ejecutará en respuesta al evento. Este método deber ser siempre público y, en el caso de los eventos predefinidos, sin argumentos (con alguna excepcion).
Hay que tener en cuenta que estos métodos se ejecutan en el mismo hilo que la traza principal, de forma que hasta que no terminen de ejecutarse la aplicación no continuará, por lo que hay que tener cuidado.

@Observe("nombre-del-evento")
public void metodoEscuchador() {
   // do domething
}

Pero aparte de los eventos predefinidos, también podemos lanzar y escuchar nuestros propios eventos.
Disponemos de varias formas de lanzar nuestros eventos personalizados:

  • Etiqueta @RaiseEvent
    Al ejecutar un método anotado con la etiqueta @RaiseEvent, se lanzará el evento indicado

    @RaiseEvent("venta-confirmada")
    public String confirmarVenta(int idVenta) {
      // cuerpo del metodo
    }

  • Fichero de navegación pages.xml
    Puedes lanzar un evento mediante la etiqueta <raise-event>

    <navigation from-action="#{manager.confirmarVenta}">
        <rule if-outcome="success">
            <raise -event type="venta-confirmada" />
        <redirect view-id="home"/>
        </rule>
    </navigation>

  • Mediante el componente Events
    Este componente permite lanzar eventos síncronos o asíncronos de manera programática.
    Los eventos lanzados de forma asíncrona se ejecutan en un hilo paralelo, por lo que son ideales para tareas pesadas o susceptibles de fallo que no deberían afectar al resto del sistema, como envío de emails, estadísticas...

    Las ventajas de usar esta manera de lanzar eventos es que, aparte de poder lanzar eventos asíncronos, se pueden añadir parámetros al evento, que serán pasados como argumentos a la función que los observe en el orden en el que se añaden. Por ello, la función observadora debe tener como máximo el mismo número de argumentos de los que se envían al lanzar el evento, y siempre el mismo tipo.
    También permite lanzar eventos al cabo de un determinado tiempo o lanzarlos al final de la transacción en curso.

    Aquí vemos un ejemplo que ejecuta un método a través de eventos cada vez que se confirma una venta.

    public String confirmarVenta(int idVenta) {
        Events.instance().raiseEvent("venta-confirmada", idVenta);
    }

    @Observe("venta-confirmada")
    public void observarVentaConfirmada(int idVenta) {   
        // do domething
    }

    También podemos optar por lanzar el evento de forma asíncrona y/o añadir más parámetros al evento, los cuales serán ignorado por las funciones observadoras con menor numero de argumentos

    public String confirmarVenta(int idVenta) {
        Events.instance().raiseEvent("venta-confirmada", idVenta, idVendedor);
    }

    @Observe("venta-confirmada")
    public void registraVenta(int idVenta) {
        // solo nos interesa el identificador de venta
    }

    @Observe("venta-confirmada")
    public String otorgaComisionAlVendedor(int idVenta, int idVendedor) {
        // Obtenemos ambos parámetros
    }

Leave a Comment

SEO-friendly URLs con Seam II

Vamos a ampliar la información del post anterior con conceptos más avanzados.
Partimos de la base de que tenemos configurado y funcionando el módulo UrlRewrite.

El primer problema con el que me topé es que mi aplicación usaba identificadores numéricos, tipo .../product.seam?idProduct=123. Por lo tanto necesitaba traducir el identificador numerico a una palabra descriptiva del producto, como .../product/silla o .../product/mesa.

Afortunadamente con Seam eso no presenta mucha dificultad y puede emplearse una EL Expression tipo #{rewriter.fromNameToId('silla')}.
Por desgracia, en el archivo urlrewrite.xml no pueden incluirse EL Expressions.

Para solucionarlo, creé una 'página virtual' en el pages.xml que recogía los parámetros como String de la url y mediante una llamada a un componente de Seam los traducía en el correspondiente identificador numérico, aprovechando que en el archivo pages.xml sí que se pueden usar EL Expressions.

  • Fichero urlrewrite.xml
    <urlrewrite>
       <rule>
         <from>^/pruduct/([A-Za-z]*)$</from>
         <to last="true">/virtual_product.seam?name=$1</to>
       </rule>
     </urlrewrite>

  • Fichero pages.xml
    .....
    <page view-id="/virtual_product.xhtml" action="#{product.init}">
        <param name="name"/>
        <navigation>
            <redirect view-id="/product.seam">   
                <param name="idProduct" value="#{urlRewriter.idProductFromName(name)}"/>
            </redirect>
        </navigation>
    </page>
    .....

  • Componente rewrite
    public int idProductFromName(String name) {
        // Consulta en la bbdd para halar
        // el id del producto a partir del nombre
    }

Leave a Comment

SEO-friendly URLs con Seam

Hoy en día están muy de moda las aplicaciones web y blogs con URLs amigables, tipo http://seamcity.madeinxpain.com/archives/category/seam.

Estas URLs además de ser más intuitivas y agradables a la vista, también son más agradables para buscadores de internet como google (o podríamos decir más agradables para EL BUSCADOR de internet).Esta es una de las técnicas seo más simples, destinadas a mejorar tu posicionamiento en buscadores.

En Seam podemos obtener estas URLs sencillas a través del filtro URLrewrite. Este filtro básicamente se encarga de traducir URLs, y es muy muy fácil de usar.
Una de las principales ventajas es que es una capa independiente que se coloca por encima de la aplicación. No es necesario modificar ni una línea de código ya existente, simplemente definir cómo se van a traducir las direcciones.

  1. Añadir el archivo urlrewrite.jar que viene incluido en la distribución de Seam en la carpeta WEB-INF/lib del proyecto. La mejor forma de hacerlo es mediante el archivo de ant que crea el seam-gen
  2. Configurar el filtro UrlRewrite en el web.xml tal y como se indica aqui
  3. Crear el fichero urlrewrite.xml que contiene las reglas de traduccion de urls en la carpeta WEB-INF. Aqui pongo el fichero del ejemplo seambay, pero lo mejor es mirar la documentacion oficial
    <!DOCTYPE urlrewrite
        PUBLIC "-//tuckey.org//DTD UrlRewrite 3.0//EN"
        "http://tuckey.org/res/dtds/urlrewrite3.0.dtd">

    <urlrewrite>

       <rule>
         <from>^/feedback/([A-Za-z0-9]*)$</from>
         <to last="true">/feedback.seam?member=$1</to>
       </rule>
       
       <rule>
         <from>^/itemdetail/([0-9]*)$</from>
         <to last="true">/auction.seam?id=$1</to>
       </rule>
       
       <rule>
         <from>^/bidhistory/([0-9]*)$</from>
         <to last="true">/bidhistory.seam?id=$1</to>
       </rule>

    </urlrewrite>

En otro post esplicaré con más detalle las posibilidades del fichero urlrewrite.xml, cómo usar parámetros, cómo traducir de nombres a identificadores numéricos, y cómo conseguir que las urls amigables las pueda generar nuestra aplicación tras el envío de formularios o acciones.

Leave a Comment

Marcas de Google Maps con menu contextual

En un post anterior hablaba de como añadir un tooltip personalizado a nuestras marcas de google maps, que permitia mostrar cualquier objeto DOM como si fuese un tooltip de la marca.

Siguiendo la misma idea, podemos hacer un menu contextual para las marcas, que se active al hacer click sobre ellas, y contenga acciones a ejecutar sobre la marca en cuestion.

Para ello seguiremos los siguientes pasos:

  1. Crear el menu y sus acciones
  2. Asignar el menu a la marca, y una referencia de la marca al menu para poder ejecutar acciones sobre ella
  3. Mostrar el menu con el evento onclick y ocultarlo al seleccionar una opción

ver ejemplo

Leave a Comment

Marcas de Google Maps con tooltip personalizado

Al situar el ratón encima de una imagen o cualquier elemento del DOM, éste es capaz de mostrar en un tooltip el texto de su atributo 'title',

ejemplo

En las marcas de Google Maps este efecto puede conseguirse especificando el atributo 'title' en el constructor de la marca.

var coords = new GLatLng(41.647471, -0.885569);
var mark = new GMarker(coords,  {title:'texto de ejemplo'})

ver ejemplo

El problema es que esta opción es muy limitada, ya que se restringe al uso de texto plano. Lo ideal sería poder mostrar cualquier elemento del DOM que queramos, con su HTML y su CSS.
Para conseguirlo vamos a seguir estos pasos:

  1. Crear el nodo del DOM que queramos mostrar o coger un nodo ya existente y ponerlo como un atributo de la marca
  2. Al escuchar el evento mouseover de la marca, añadir el nodo al div contenedor del mapa en el la posición en la que se encuentra la marca
  3. Al escuchar el evento mouseout de la marca, eliminar el nodo del div contenedor del mapa para ocultarlo.

ver ejemplo

Leave a Comment

Cómo limitar el zoom y el desplazamiento en Google Maps

En ocasiones nos interesa restringir el desplazamiento del usuario sobre el mapa a un área determinada, o limitar los niveles de zoom máximo y mínimo.

En el caso del zoom la solución es bastante sencilla. Solo hay que sobreescribir los métodos getMinimumResolution() y getMaximumResolution() de cada uno de los tipos de mapas GMapType presentes en nuestro mapa.
Para el caso del desplazamiento, la solución es algo más compleja. Consiste en escuchar el evento move, que es lanzado por el objeto GMap cuando hay un desplazamiento, y comprobar las coordenadas del mapa tras el desplazamiento. Si estas coordenadas quedan fuera del área a la que queremos limitar el desplazamiento, usamos el metodo GMap.setCenter para que el mapa quede dentro de nuesta área.

Esta clase implementa la funcionalidad deseada:

/******  TRestricter  **********************************************/

// Constructor
TRestricter = function (map) {
this.map = map;
}
// Función que activa la limitación del desplazamiento entre la esquina inferior izquierda
// y la esquina superior derecha
TRestricter.prototype.restrict = function (sw, ne) {
this.map._allowedBounds = new GLatLngBounds(sw, ne);
GEvent.addListener(this.map, 'move', this.checkBounds);
}
// Función que desactiva la limitación del desplazamiento
TRestricter.prototype.unrestrict = function () {
this.map._allowedBounds = null;
}
// Listener encargado de comprobar el desplazamiento
TRestricter.prototype.checkBounds = function() {
if (!this._allowedBounds || this._allowedBounds.contains(this.getCenter())) return;
var x = Math.min(Math.max(this.getCenter().lng(), this._allowedBounds.getSouthWest().lng()), this._allowedBounds.getNorthEast().lng());
var y = Math.min(Math.max(this.getCenter().lat(), this._allowedBounds.getSouthWest().lat()), this._allowedBounds.getNorthEast().lat());
this.setCenter(new GLatLng(y,x));
}
// Establece los límites de zoom del mapa
TRestricter.prototype.zoomLevels = function (min, max) {
var array = this.map.getMapTypes() || [];
for (var i=0; i
array[i].getMinimumResolution = function () { return min };
array[i].getMaximumResolution = function () { return max };
}
}

Y aquí tenemos un ejemplo de la clase en acción

Leave a Comment

Arrays asociativos en javacript

En javascript se llama array asociativo a aquellos que en vez de estar organizados con indices numericos en funcion de su posicion dentro del array, están organizados por claves no numericas.
Este tipo de arrays puede ser muy útil si tenemos listas de objetos que poseen un identificador.

<br />
var array_asociativo =  new Array();<br />
array_asociativo['uno'] = 'brinkindans';<br />
array_asociativo['dos'] = 'crusaito';<br />
array_asociativo['tres'] = 'maiquel yason';<br />
array_asociativo['cuatro'] = 'robocó';</p>
<p>alert(array_asociativo('tres')); // -->  maiquel yason<br />

Como se puede obervar este tipo de arrays se acerca mas al concepto de Map que al de Array.
Esto ocurre porque los arrays en javascript heredan de la clase Object.
El codigo anterior seria equivalente a este:

<br />
var array_asociativo =  new Object();<br />
array_asociativo.uno = 'brinkindans';<br />
array_asociativo.dos = 'crusaito';<br />
array_asociativo.tres = 'maiquel yason';<br />
array_asociativo.cuatro = 'robocó';</p>
<p>alert(array_asociativo('tres'))//-->  maiquel yason<br />

En ambos casos podemos acceder a sus propiedades utilizando la notación de paréntesis o la notacion de puntos.

<br />
alert(array_asociativo('uno')); //-->  brinkindans<br />
alert(array_asociativo.dos)//-->  crusaito<br />

El principal problema con estos arrays viene cuando queremos iterar por ellos. Al no estar organizados por indices, no nos vale el típico "for( i=0; i Deberemos usar un "for in"

<br />
for(var i in array_asociativo) {<br />
  alert(i)// Nombre de la clave<br />
  alert(array_asociativo(i)); // valor<br />
}<br />

Sin embargo al iterar de esta forma no solo se enumeran las propiedades del objeto en cuestión, si no también sus funciones por lo que deberíamos hacer una comprobación de tipos mediante el operando "typeof". Este operando devuelve un String con el tipo del operador sobre el que se ejecuta.
Aqui están los resultado al ejecutar el operador "typeof" sobre cada uno de los tipos de javascript.

<br />
typeof new Object() //-->   'object'<br />
typeof new Array() //-->     'object' , ya que los arrays heredan de los objetos<br />
typeof alert //-->   'function', ya que alert() es una función<br />
typeof 'cadena' //-->   'string', ya que 'cadena' es un String<br />
typeof 21 //-->   'number', ya que es un numero<br />
typeof true //-->    'boolean'<br />
typeof null //-->    'object', null también es considerado como un objeto<br />
typeof undefined //-->    'undefined'<br />

Leave a Comment