ver la traza de log del servidor al hacer test de una app con nuxeo

in Sin categoría

Para ver la traza de la consola del servidor que despliega nuxeo para realizar los tests, debemos establecer la configuracion adecuada de log4j en el archivo src/test/resources/log4j.properties

De esta forma podemos ver los mesajes de información que escupe el servidor al iniciar el runtime de nuxeo y comprobar que nuestras contribuciones se están desplegando correctamente


				
0 Comments

Campos encriptados en base de datos con Grails

in Sin categoría

Para comunicarse con la base de datos, grails utiliza el magnífico gorm, un wrapper de hibernate .

Para almacenar datos encriptados en base de datos que podamos desencriptar posteriormente (o sea, encriptado con un cifrado simetrico, no un hash) debemos especificar a grom en el mapping que nuestro campo password es de tipo encriptado

Lo mejor es empezar creando un Codec de grails que nos permita facilmente encriptar y desencriptar una cadena de texto con una simple sentencia como:

"mi texto a encriptar".encodeAsSecure()
"mi texto a DESencriptar".decodeSecure()

Crearemos nuestro codec SecureCodec en la carpeta grails-app/utils

class SecureCodec {
    String password = "clave_de_cifrado_que_no_deberiamos_guardar_aqui"

    static encode = { str ->
        if(['null', 'Null', 'NULL', '', null].contains(str)) str = ''
        try {
            Cipher cipher = setupCipher(Cipher.ENCRYPT_MODE, password)

            // encriptamos
            byte[] encodedBytes = cipher.doFinal(str.getBytes())

            // pasamos a hexadecimal
            String hex = new String(new Hex().encode(encodedBytes))?.toUpperCase()
            return hex;
        } catch(Exception e) {
            return str
        }
    }

    static decode = { hex ->
        try {
            // pasamos de hexadecimal a bytes
            byte[] bytes = new Hex().decodeHex((char[])hex)

            // desencriptamos
            Cipher cipher = setupCipher(Cipher.DECRYPT_MODE, password)
            def decripted = new String(cipher.doFinal(bytes))

            return decripted
        } catch(Exception e) {
            return hex
        }
    }

    private static setupCipher(mode, password) {
        Cipher cipher = Cipher.getInstance("AES");

        // recortamos el pass a 16 caracteres - 128 bits
        byte[] keyBytes = new byte[16];
        byte[] b = password.getBytes();
        int len = b.length;
        if (len > keyBytes.length)
            len = keyBytes.length;
        System.arraycopy(b, 0, keyBytes, 0, len);

        // creating SecretKeySpec
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");

        cipher.init(mode, keySpec);
        return cipher
    }

}

Luego, crearemos nuestro tipo de mapeo que debe implementar la interfaz org.hibernate.usertype.UserType
en la carpeta src/groovy

class EncryptedStringType implements UserType {

    Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws SQLException {
        String value = rs.getString(names[0])

        if (!value) {
            return ''
        }

        return value.decodeSecure()
    }

    void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException {
        value = value ?: ''
        if (value != null) {
            String encrypted = value.toString().encodeAsSecure()
            st.setString index, encrypted
        }
        else {
            st.setNull(index, Types.VARCHAR)
        }
    }

    Class returnedClass() { String }

    int[] sqlTypes() { [Types.VARCHAR] as int[] }

    Object assemble(Serializable cached, Object owner) { cached.toString() }

    Object deepCopy(Object value) { value.toString() }

    Serializable disassemble(Object value) { value.toString() }

    boolean equals(Object x, Object y) { x == y }

    int hashCode(Object x) { x.hashCode() }

    boolean isMutable() { true }

    Object replace(Object original, Object target, Object owner) { original }
}

Ya tenemos todo lo necesario. Ahora solo nos queda decirle a gorm que mapee nuestro campo personalData como tipo EncryptedStringType

class Usuario {
    String username
    String personalData

    static mapping = {
        personalData type:'EncryptedStringType'
    }
}

Y ya esta. Ahora si miramos el texto guardado en nuestra bd, saldrá algo como ’13B2C7531DD55C97F250A1BA50C39BB4′

De forma transparente, al cargar el objeto con gorm, tendremos acceso a nuestro campo personalData como texto en claro.

El principal problema es que perdemos la capacidad de hacer búsquedas directas usando wildcards ‘%’, debido a la naturaleza de la encriptacion. Para conseguirlo hay que dar un pequeño rodeo, comparando la cadena desencriptada de la bd con lo que queramos buscar:

sql.findAll("from Usuario where UPPER( CONVERT( AES_DECRYPT(UNHEX(u.user) , 'mi_password'), CHAR)) like " + " '%texto_a_buscar%' ")
0 Comments

Cambiar las opciones por defecto de impresión de firefox

in General

Cuando imprimes una página web con firefox, por defecto se imprime una cabecera indicando la URL y un pie.

Estas opciones se pueden cambiar a la hora de imprimir, pero no se ofrece ninguna opción para cambiar su valor por defecto. Para ello deberemos ir a la página de configuración, poniendo en la barra de direcciones ‘about:config’

Una vez alli, pondremos en el filtro ‘print.print_header’, y veremos tres pares de clave/valor, que es lo que se imprimira en la cabecera, en la zona izquierda, centro y derecha. Si queremos que no se imprima nada, simplemente deberemos poner estos valores en blanco.

Para quitar lo que se imprime en el pie de la página, procederemos de la misma forma, pero filtraremos por ‘print.print_footer’

0 Comments

Errores com.sun.facelets.impl.DefaultFacelet refresh was modified, flushing component applied

in jsf

Este es uno de esos errores que te pueden volver locos, de esos que dices ‘en mi ordenador funcionaba, lo prometo’.

El problema es que el ordenador de desarrollo y el servidor tienen distinta hora. La hora del servidor está retrasada respecto a la de desarrollo.

Esto provoca que al modificar un archivo del war en desarrollo, la fecha de última modificación queda puesta a, por ejemplo, las 12:00. Al desplegar este war en el servidor, la fecha de modificación se mantiene, pero la hora del servidor es, por ejemplo las 10:00. Por lo que el fichero ha sido modificado en el futuro!!!

Si intentas acceder a la aplicación web con el navegador, el jboss detecta que el archivo ha sido modificado e interpreta que es una versión obsoleta del fichero e intenta recargarlo. Pero al recargarlo, la fecha de modificación le sigue diciendo que ha sido modificado.

Una solución es, obviamente, poner bien la hora del servidor. Pero si no es posible por algún extraño motivo, también puedes añadir un parámetro en el web.xml diciendo cada cuanto se tienen que buscar nuevas versiones del archivo

<context-param>
<param-name>facelets.REFRESH_PERIOD</param-name>

<param-value>0</param-value>
</context-param>

0 Comments

SELECT LIKE con wildcards en JPA

in General

Si queremos hacer una consulta de un campo de texto y queremos usar %wildcards%m la primera idea es hacer algo como

Query q = entityManager.createQuery("SELECT p FROM Pais p WHERE p.nombre LIKE '%:nombre%' ");

q.setParameter("nombre", "españa");

De esta forma dará un error diciendo que no encuentra el parámetro 'nombre'. Esto es porque está puesto entre comillas en el ejbql, de forma que lo interpreta como un literal.

Lo correcto sería hacer, haciendo además que no distinga entre mayúsculas y minúsculas

Query q = entityManager.createQuery("SELECT p FROM Pais p WHERE LOWER(p.nombre) LIKE :nombre ");

q.setParameter("nombre", "%spañ%".toLowerCase());

1 Comment

JBPM: “couldn’t get acquirable jobs” Error ORA-00933

in General

Este error aparece cuando estás trabajando con una base de datos Oracle y en el ficherohibernate.cfg.xml has especificado un Dialect erróneo.
Suele manifestarse con la siguiente excepción:

ERROR [JobExecutorThread] exception in job executor thread. waiting 320000 milliseconds
org.jbpm.JbpmException: couldn't get acquirable jobs
Caused by: java.sql.SQLException: ORA-00933: comando SQL no terminado correctamente

La propiedad correcta del dialecto para Oracle 9 es:

<property name="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</property>

2 Comments

Performance: Porqué usar @Factory en vez de getters

in Seam

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();
}
}

...
#{user.name}
...

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;
}
}

// invocamos la variable userList directamente
...
#{user.name}
...

0 Comments

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

in Seam

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..

0 Comments

Cambiar el usuario y password de subversion en eclipse

in 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

1 Comment

Eventos en Seam

in 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>

  • 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
    }

0 Comments