viernes, 31 de agosto de 2012

Deshabilitar el parámetro jsessionid

Java
En las aplicaciones web java cuando un usuario accede por primera vez a una página de la aplicación se le añade a los enlaces que se generan el parámetro jsessionid para que en caso de que se tengan las cookies desactivadas poder mantener y determinar la sesión del usuario. Esto puede ser un problema ya que los rastreadores de los buscadores indexarán las páginas con ese parámetro añadido a la URL.

Para desactivar esta reescritura de los enlaces no hay más que añadir en el archivo web.xml de la aplicación lo siguiente:

<session-config>
    <tracking-mode>COOKIE</tracking-mode>
</session-config>

La cabecera del web.xml debe ser la siguiente, es decir, se necesita la versión 3.0 de servlets API soportada por Tomcat 7.0 entre otros servidores:

<web-app version="3.0"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

Referencia:
http://andrius.miasnikovas.lt/2010/07/whats-new-in-tomcat-7/
http://stackoverflow.com/questions/962729/is-it-possible-to-disable-jsessionid-in-tomcat-servlet

viernes, 24 de agosto de 2012

Páginas PageCatalog, ServiceStatus e HibernateStatistics de Tapestry

Apache Tapestry
Como ya he comentado en alguna ocasión anterior una de las buenas características de Tapestry es la buena información que dá cuando algo va mal con la página de Informe de error (Exception Report). También podemos obtener un montón de trazas (I, II) para conocer que es lo que está haciendo internamente en cada momento y que ayuda a resolver los problemas cuando los hay más rápidamente.

Otra buena fuente de información además de la página de un informe de error y el sistema de trazas están las páginas incluidas en el propio framework (builtin) PageCatalog, ServiceStatus y la página de estadísticas de Hibernate.

La página PageCatalog permite monitorizar el estado de las páginas de la aplicación, cuantas ha sido cargadas, que idiomas se están usando, cuantas veces se han usado, cuanto tiempo han tardado en construirse, cuantos componentes tienen y alguna cosa más. Otra cosa interesante acerca de esta página es que permite cargar todas las páginas de la aplicación permitiendo descubrir rápidamente si alguna contiene errores.


La página ServiceStatus muestra toda la lista de servicios encontrados en los diferentes módulos. Ahí podemos ver su identificativo, la interfaz que implementa, su ámbito y su estado. Para monitorizar una aplicación o descrubrir algún tipo extraño de error quizá nos resulte interesante conocer esta información.


Otra muy buena página que tapestry nos proporciona «out-of-the-box» cuando usamos Hibernate es la completa página de estadísticas del mismo. En ella obtendremos una extensa información sobre el estado de Hibernate que nos permiritá monitorizar, detectar posibles fuentes de problemas o puntos donde nuestra aplicación no so comporta de forma óptima y con ello ajustar las caches o nuestra aplicación para proporcionar un mayor rendimiento o disminuir el consumo de recursos. Los apartados de esta página son las estadísticas de Session Factory, Collections, Queries y Second Level Cache. El significado de los datos proporcionados puede consultarse en el enlace anterior. En las siguientes imágenes puede verse la página de una aplicación nada más iniciarse y después de un poco de uso, se aprecia como van aumentando los hits, misses, load count, etc...


Para obtener esta información es necesario modificar el archivo de configuración de Hibernate añadiendo la propiedad «hibernate.generate_statistics» con valor «true».

<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">    
<hibernate-configuration>
 <session-factory>
    ...
    <property name="hibernate.generate_statistics">true</property>    
  </session-factory>
</hibernate-configuration>

Estas páginas son únicamente accesibles por defecto desde la máquina local (127.0.0.1) y en el modo desarrollo por seguridad.

Referencia:
Documentación sobre Apache Tapestry
http://tapestry.apache.org/service-status.html

viernes, 17 de agosto de 2012

Desempaquetado («unboxing») de Amazon Kindle Touch

Amazon.es
Desde hace unos meses el lector de libros electrónicos de Amazon Kindle Touch ya está disponible para la compra desde la página española de Amazon. Una de mis dudas cuando compré el Kindle Touch era como era la experiecia de lectura, como convertir libros en formato pdf o formato ebook epub/mobi/doc a los nativos del libro electrónico, como quedaban esas conversiones y si en una página entraba el mismo texto que en un libro tradicional. Otra de mis dudas era como se leerían los pdfs de libros técnicos, cambiando la orientación de lectura a horizontal se dejan leer bastante bien aunque hay que hacer varios pasos de página en el kindle para leer una página del libro. Para mi, la experierincia es lo suficientemente buena como para haber merecido la compra a pesar de tener algunos contras.

Un lector de libros tiene varias ventajas sobre los libros tradicionales entre ellas:
  • Los libros suelen tener un precio más bajo que los libros en papel.
  • Permite tomar notas, subrayar párrafos y relizar búsquedas.
  • Permite modificar el tamaño y tipo de la fuente según nuestras preferencias o necesidades.
  • Los libros electrónicos no ocupan espacio físico y no hay que almacenarlos despues de una lectura (da pena adquirir un objeto que normalmente solo le vamos a dar un solo uso).
  • Hasta ahora solía ir a la bibilioteca municipal para obtener los libros, ahora no tengo que desplazame hasta ella y no tengo que esperar si hay otro usuario de la bilbioteca leyendo el libro que quiero aunque me pierdo la sensación de entrar a un «templo del conocimiento».
Y en la parte de contras están:
  • Las conversiones al formato nativo no son perfectas y el texto no queda igual que el libro físico.
  • Las conversiones para que queden aceptables requieren que les dediquemos parte de nuestro tiempo.
  • Los libros que incluyen alguna imagen como un mapa de la historia no se leen muy bien por lo pequeño que se ven.
  • Las últimas novedades puede que no las encontremos en formato electrónico. Aunque cualquier libro con ya un tiempo y algo conocido suele encontrarse.
  • En mi opinión, la lectura del libro tradicional sigue siendo mejor que la del libro electónico. Pero en los próximos años seguramente vendrán mas mejoras como pantallas de libro electŕonico a color, mayor velocidad de refresco que los harán útiles para ver animaciones o incluso vídeos.
  • Al ser táctil la pantalla acaba por ensuciarse con la grasilla de los dedos y de vez en cuando hay que limpiarla aunque que con un plástico protector puede ser suficiente.
En cuanto al Kindle Touch es bastante fino y pequeño ya que no tiene teclado físico, prácticamente es todo pantalla con unos bordes no muy grandes, pero al ser táctil la pantalla hay que sujetarlo de forma adecuada para no pasar de página sin querer. Su peso es bastante ligero y no cansa tenerlo en la mano. El pasar de página es rápido y no entorpece la lectura. La batería dura bastante, de sobra para leer un libro entero de unas 300-350 páginas en un libro físico con una sola carga. El transmitir los libros electrónicos al Kindle lo podemos hacer de dos formas, conectándolo mediante un cable USB al ordenador y copiando archivos como si fuera una memoria USB o enviándolos adjuntos en un correo electrónico a una dirección de correro electrónico que proporciona Amazon para nuestro Kindle y que se copia automáticamente en el Kindle cuando activamos el WIFI. La carga de la batería se hace mediante un cable micro-USB con cualquier cargador de móvil, con el Kindle viene un cable adaptador USB a micro USB aunque el cargador lo tendremos que comprar a parte si como digo no tenemos un cargador de móvil.

También cuando compré el kindle adquirí una funda Belkin que tiene cierto acolchado. Esta era de las más barata que hay y aún así sigue siendo un poco cara para lo que es.

En otra entrada explicaré como realizar las conversiones para que queden lo mejor posible para la lectura utilizando los programas CalibreSigil o Mukomix (una pequeña utilidad que he desarrollado).

Aquí dejo una colección de imagenes del desempaquetado y como se ve un libro en el Kindle Touch:


















Si prefieres ver un vídeo del unboxing del kindle touch en el enlace anterior se puede ver junto con la comparación con el kindle no táctil.


Después de dos años de que compré el Kindle Touch Amazon ha sacado al mercado dos versiones mejoradas denominadas Kindle Paperwhite que ofrecen más resolución (de 600x800 del Touch a 758x1024 del Paperwhite) y con un mayor contraste con su pantalla más blanca proporcionada por una leve iluminación entre otras mejoras, en la wikipedia pueden compararse y ver las características de los diferentes modelos que Amazon ha lanzado hasta el momento.


Amazon Kindle Touck
Revisión por picodotdev el .
Galería de fotos y comentarios del desempaquetado del libro electrónico
Valor: 5

Referencia:
http://www.amazon.es/
Kindle Paperwhite

viernes, 10 de agosto de 2012

Análisis estático de código con PMD

PMD
Si seguimos las buenas prácticas de las metodologías ágiles en los proyectos que desarrollemos realizaremos pruebas unitarias y de integración a la vez que desarrollamos nuestro código. Ello nos permitirá no tener que realizar pruebas manualmente y asegurarnos de que las cosas siguen funcionando cuando hacemos cambios, si además disponemos de en servidor de integración continua las pruebas se podrán lanzar para cada cambio detectado en la herramienta de control de versiones que usemos.

Las pruebas unitarias y de integración son una cosa deseable y para algunos imprescindible, sin embargo, solo nos dicen que las partes probadas funcionan como se espera, no analiza nuestro código fuente. Analizar el código fuente puede proporcionarnos información para encontrar posibles errores, código muerto y que no se usa, código ineficiente, código demasiado complejo y código duplicado, ... que pasado el tiempo y con las diversas modificaciones seguramente tendremos. Siguiendo las indiciones de una herramienta de análisis de código podemos detectar esos posibles problemas de forma automatizada. Además, es seguro que en un equipo que desarrollan varias personas cada una codifique de una forma diferente, con una herramienta de estas como Checkstyle podemos establecer reglas de estilo de tal forma que consigamos un código más homogeneo y de nuevo más mantenible para todos.

Una de las herramientas más conocidas en la plataforma Java es PMD. Se puede integrar con la mayoría de IDEs y herramientas como Ant y Maven. Si queremos automatizar las pruebas lo integraremos con Ant, Maven o Gradle para no depender de un IDE y realizar las pruebas en la construcción de nuestro proyecto asi como poder ejecutarlas en el servidor de integración continua. Estos serían los trozos necesarios para integrar PMD con Gradle.

...
configurations {
    pmd
}

dependencies {
 ...
 pmd 'net.sourceforge.pmd:pmd:5.0.0'
 ...
}
...
task pmd << {
 ant.mkdir(dir:'build/pmd')
 ant.taskdef(name: 'pmd', classname: 'net.sourceforge.pmd.ant.PMDTask', classpath: project.configurations.pmd.asPath)
 ant.pmd {
  ruleset('java-basic,java-braces,java-codesize,java-naming,java-optimizations,java-unusedcode')
  formatter(type: 'html', toFile: 'build/pmd/pmd.html')
  fileset(dir: 'src/main/java') {
   include(name: '**/*.java')
  }
 }
}
...

Este es el informe que genera PMD para la aplicación Hola mundo con Tapestry 5.


En el siguiente enlace podemos ver las reglas ya predefinidas y que podemos aplicar a nuestro código. Si queremos analizar código groovy tenemos la herramienta CodeNarc.

Referencia:
http://pmd.sourceforge.net/
http://pmd.sourceforge.net/ant-task.html
http://pmd.sourceforge.net/rules/index.html
http://checkstyle.sourceforge.net/
http://www.gradle.org/
Elegir herramientas para un proyecto Java

viernes, 3 de agosto de 2012

JodaTime y como integrarlo con Hibernate

Java
Las clases proporcionadas en la API de Java para trabajar con fechas tienen varios defectos. JodaTime es una librería alternativa a las clases del JDK que trata de arreglarlos o no tenerlos, sus características son las siguientes:
    Hibernate
  • Fácil de usar: dispone de métodos para acceder de forma sencilla al año o al día del año entre otros.
  • Más fácil de exterder: se le pueden añadir nuevos calendarios de forma más sencilla que la necesaria en la API de Java.
  • Conjunto de características completo: proporciona mayor funcionalidad para el cálculo de fechas.
  • Interoperabilidad sencilla: es fácil de realizar conversiones entre el JDK y JodaTime.
  • Mejor rendimiento: solo realiza las operaciones minimas necesarias.
  • Documentación completa: tiene una guía de usuario y una API en javadoc
  • Madurez: está ampliamente probada desde su inicio de desarrollo en el 2002.
  • Código abierto: tiene una licencia Apache.
Aparte de estas características una de las más destacadas es que facilita la automatización de casos de pruebas que dependan de fechas ya que podemos establecer que la fecha altual de joda time sea una que esté en el pasado o en el futuro de forma sencilla (esto es haciendo new JodaTime() en vez de tener la fecha del sistema podemos hacer que la fecha sea la que queramos). Solo por esto ya merece la pena usarla aunque hay otras razones. Además de poder representar fechas con JodaTime pueden ser representados otros conceptos:
  • Instante: Representa un instante en el tiempo medido en milisegundos transcurridos desde el 1970-01-01T00:00Z.
  • Parcial: Un parcial no contiene contiene todos los campos pero añadiendoselos se puede obtener un instante (parcial + campos faltantes + zona horaria = instante).
  • Intervalo: Representa el periodo de tiempo de un instante a otro instante.
  • Duracion: Representa una duración de tiempo medido en milisegundos, instante + duracion = instante.
  • Periodo: Representa un perido de tiempo medido en terminos de campos como años, meses, días u horas. Un periodo de tiempo puede ser inexacto en términos de tiempo ya que tiene en cuenta cosas como los años bisiestos o los cambios de hora.
Además puede se integrada perfectamente con Hibernate de tal forma que en nuestras entidades de dominio podamos tener por ejemplo propiedades del tipo DateTime entre otras, evintandonos conversiones entre nuestro modelo y la representación con la que realizamos los cálculos y operaciones. Lo primero que deberemos hacer para tener esta integración entre Hibernate y JodaTime es incluir la propia librería de JodaTime y el soporte para Hibernate como dependencias a nuestro proyecto. Son las siguientes:
  • joda-time:joda-time:2.1
  • joda-time:joda-time-hibernate:1.3
La librería joda-time-hibernate proporciona soporte para la versión 3.6 de hibernate, si usamos hibernate 4 deberemos usar usertype:
  • org.jadira.usertype:usertype.parent:3.0.0.CR1
Ya usemos hibernate 3.6 o hibernate 4 y una u otra librería su uso es muy similar en ambas y se hace a través de anotaciones, aunque tambien se puede hacer a traves de los ficheros de mapeo de hibernate, las anotaciones son más recomendables y son de la siguiente forma:

...
import org.joda.time.DateTime;
...
@Type(type = "org.joda.time.contrib.hibernate.PersistentDateTime")
private DateTime fecha;
...

No hay más que usar la anotación @Type, es así de sencillo y una migración de los Date del JDK a DateTime de JodaTime no de debería ser muy complicada con la ayuda de un buen editor.

Si queremos establecer la fecha actual en el pasado o en el futuro podemos hacer uso del método de utilidad DateTimeUtils.setCurrentMillisFixed.

Referencia:
http://joda-time.sourceforge.net/contrib/hibernate/index.html
https://forums.oracle.com/forums/thread.jspa?threadID=1982257&start=0&tstart=0
http://joda-time.sourceforge.net/contrib/hibernate/userguide.html
http://usertype.sourceforge.net/

viernes, 27 de julio de 2012

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

Java
Hace un tiempo comenté algunas ventajas que obtenemos al tener un sistema de logging en una aplicación y una combinación de librerías que podríamos utilizar para este propósito [1]. Una vez que tenemos un sistema de logging en una aplicación podemos sacarle mucho provecho, como obtener alguna información básica del entorno de ejecución.

La razón de obtener la mayor información del entorno de ejecución es más para el entorno de pruebas o producción que para el entorno en el que desarrollamos, ya que estos primeros pueden llevar alguna configuración especial que no solemos revisar o están fuera de nuestro control aunque en los últimos también puede ser interesante si las hay varias personas que colaboran con posiblemente diferentes sistemas operativos instalados y configuraciones. Tener a nuestra disposición esta información nos puede permitir darnos cuenta de la causa de algun comportamiento extraño. Algún ejemplo de información que nos puede ser de utilidad es la hora del sistema, si trabajamos con fechas la zona horaria en la que está configurado o la codificación de caracteres por defecto.

Vamos a verlo en el caso de una aplicación web. En las aplicaciones web Java tenemos a nuestra disposición dos interfaces que podemos implementar y ser notificados cuando ocurran ciertos eventos en la aplicación como son cuando se inicia, cuando se va a terminar, cuando se crea una sesión y cuando se destruye una sesión. Ahora que estamos interesandos en obtener información del sistema vamos a ver unos ejemplos de clases ServletContextListener y HttpSessionListener que podríamos utilizar para obtener información.

Mediante la interfaz ServletContextListener y sus métodos contextInitialized y contextDestroyed podremos obtener una referencia a un objeto ServletContext y podremos acceder a los métodos que proporciona para por ejemplo conocer los parámetros de inicialización de la aplicación si esta la parametrizamos de esta manera. En el siguiente ejemplo se saca al log la siguiente información: unas trazas de cabecera para saber donde empieza en la salida del log cada inicio de la aplicación, el nombre de contexto de la aplicación, las propiedades del sistema (incluyendo la zona horaria y la codificación de caracteres por defecto), la información del servidor sobre el que se ejecuta la aplicación y la versión de la Java Servlet API, los nodos del arbol JNDI y un enumerado que identificará el servidor de aplicaciones sobre el que se ejecuta la aplicación.

package es.com.blogspot.elblogdepicodev.misc;

import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

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

import es.com.blogspot.elblogdepicodev.Entorno.Servidor;

/**
 * Clase que recibe los eventos de inicio y fin de la aplicación web.
 */
public class ContextListener implements ServletContextListener {

 private static Logger logger;

 public static ServletContext SERVLET_CONTEXT;

 @Override
 public void contextInitialized(ServletContextEvent sce) {
  SERVLET_CONTEXT = sce.getServletContext();

  // Configurar el entorno
  try {
   Utilidades.setupEntorno();
  } catch (Exception e) {
   e.printStackTrace();
  }

  logger = LoggerFactory.getLogger(ContextListener.class);

  logger.info("");
  logger.info("--- -.-. . -. ... ..- .-. .- -.. --- -.-. . -. ... ..- .-. .- -.. --- -.-. . -. ... ..- .-. .- -.. ---");
  logger.info("--- -.-. . -. ... ..- .-. .- -.. --- -.-. . -. ... ..- .-. .- -.. --- -.-. . -. ... ..- .-. .- -.. ---");
  logger.info("--- -.-. . -. ... ..- .-. .- -.. --- -.-. . -. ... ..- .-. .- -.. --- -.-. . -. ... ..- .-. .- -.. ---");

  {
   try {
    logger.info("Iniciando aplicación web [" + sce.getServletContext().getServletContextName() + "]...");
    logger.info("Propiedades del sistema:");

    SecurityManager sm = System.getSecurityManager();
    Enumeration e = System.getProperties().keys();
    while (e.hasMoreElements()) {
     String key = (String) e.nextElement();
     try {
      if (sm != null) {
       sm.checkPropertyAccess(key);
      }
      logger.info(key + ": " + System.getProperties().get(key));
     } catch (SecurityException se) {
      logger.info(key + ": [No hay permisos]");
     }
    }
   } catch (Exception e) {
    logger.warn(e.getMessage(), e);
   }
  }

  {
   logger.info("Propiedades del entorno:");
   logger.info("Server info: " + sce.getServletContext().getServerInfo());
   logger.info("Java Servlet API version: " + sce.getServletContext().getMajorVersion() + "." + sce.getServletContext().getMinorVersion());
  }

  {
   // Obtener los nodos del árbol JNDI
   Map enviroment = null;
   try {
    enviroment = new LinkedHashMap();
    buildEnv(new InitialContext(), Constantes.JNDI_EVANDTI, enviroment);
   } catch (Exception e) {
    logger.error(e.getMessage(), e);
   }

   logger.info("Propiedades de entorno para la aplicacion web:");
   Iterator it = enviroment.keySet().iterator();
   while (it.hasNext()) {
    String key = (String) it.next();
    logger.info("{}: {}", new Object[] { key, enviroment.get(key) });
   }
  }

  {
   logger.info("Propiedades dinámicas:");
   Servidor servidor = getServidor(SERVLET_CONTEXT);
   Entorno.setPropiedadDinamica(Entorno.PROP_DINAMICA_SERVIDOR, servidor);

   Iterator it = Entorno.getPropiedadesDinamicas().iterator();
   while (it.hasNext()) {
    String propiedad = (String) it.next();
    Object valor = Entorno.getPropiedadDinamica(propiedad);
    logger.info("{}: {}", propiedad, valor);
   }
  }

  logger.info("Iniciando aplicacion con la configuracion del entorno de «{}»", Entorno.getEntorno().toString().toLowerCase());
 }

 @Override
 public void contextDestroyed(ServletContextEvent sce) {
  logger.info("Finalizando aplicación...");
 }

    /**
     * Añade en un Map los nodos de árbol JNDI configurados en el servidor.
     */
 private void buildEnv(Context c, String name, Map env) throws NamingException {
  NamingEnumeration ne = c.list(name);
  while (ne.hasMoreElements()) {
   NameClassPair n = (NameClassPair) ne.next();
   String x = name + "/" + n.getName();
   Object o = c.lookup(x);
   env.put(x, o);
   if (o instanceof Context) {
    buildEnv(c, x, env);
   }
  }
 }

 /**
  * Devuelve un enum del servidor sobre el que se está ejecutando la aplicación.
     */
 private static Servidor getServidor(ServletContext sc) {
  String si = sc.getServerInfo();

  if (si.contains(Constantes.SERVIDOR_TOMCAT)) {
   return Servidor.TOMCAT;
  } else if (si.contains(Constantes.SERVIDOR_JBOSS)) {
   return Servidor.JBOSS;
  } else {
   return Servidor.DESCONOCIDO;
  }
 }
}

Esto sería un ejemplo de salida de esta clase ContextListener:

2012-05-04 21:03:33,031 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener 
2012-05-04 21:03:33,037 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener --- -.-. . -. ... ..- .-. .- -.. --- -.-. . -. ... ..- .-. .- -.. --- -.-. . -. ... ..- .-. .- -.. ---
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener --- -.-. . -. ... ..- .-. .- -.. --- -.-. . -. ... ..- .-. .- -.. --- -.-. . -. ... ..- .-. .- -.. ---
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener --- -.-. . -. ... ..- .-. .- -.. --- -.-. . -. ... ..- .-. .- -.. --- -.-. . -. ... ..- .-. .- -.. ---
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener Iniciando aplicación web [TapestryCensurado]...
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener Propiedades del sistema:
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.runtime.name: OpenJDK Runtime Environment
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener sun.boot.library.path: /usr/lib/jvm/java-6-openjdk/jre/lib/amd64
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.vm.version: 20.0-b12
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener shared.loader: 
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.vm.vendor: Sun Microsystems Inc.
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.vendor.url: http://java.sun.com/
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener path.separator: :
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener tomcat.util.buf.StringCache.byte.enabled: true
2012-05-04 21:03:33,038 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.util.logging.config.file: /home/picodotdev/archivos/apache-tomcat-7.0.27/conf/logging.properties
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.vm.name: OpenJDK 64-Bit Server VM
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener file.encoding.pkg: sun.io
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener sun.java.launcher: SUN_STANDARD
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener user.country: ES
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener sun.os.patch.level: unknown
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.vm.specification.name: Java Virtual Machine Specification
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener user.dir: /home/picodotdev/archivos/apache-tomcat-7.0.27/bin
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.runtime.version: 1.6.0_24-b24
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.awt.graphicsenv: sun.awt.X11GraphicsEnvironment
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.endorsed.dirs: /home/picodotdev/archivos/apache-tomcat-7.0.27/endorsed
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener os.arch: amd64
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.io.tmpdir: /home/picodotdev/archivos/apache-tomcat-7.0.27/temp
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener line.separator: 

2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.vm.specification.vendor: Sun Microsystems Inc.
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.naming.factory.url.pkgs: org.apache.naming
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.util.logging.manager: org.apache.juli.ClassLoaderLogManager
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener os.name: Linux
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener sun.jnu.encoding: UTF-8
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener tomcat.util.scan.DefaultJarScanner.jarsToSkip: bootstrap.jar,commons-daemon.jar,tomcat-juli.jar,annotations-api.jar,el-api.jar,jsp-api.jar,servlet-api.jar,catalina.jar,catalina-ant.jar,catalina-ha.jar,catalina-tribes.jar,jasper.jar,jasper-el.jar,ecj-*.jar,tomcat-api.jar,tomcat-util.jar,tomcat-coyote.jar,tomcat-dbcp.jar,tomcat-i18n-en.jar,tomcat-i18n-es.jar,tomcat-i18n-fr.jar,tomcat-i18n-ja.jar,tomcat-juli-adapters.jar,catalina-jmx-remote.jar,catalina-ws.jar,tomcat-jdbc.jar,commons-beanutils*.jar,commons-codec*.jar,commons-collections*.jar,commons-dbcp*.jar,commons-digester*.jar,commons-fileupload*.jar,commons-httpclient*.jar,commons-io*.jar,commons-lang*.jar,commons-logging*.jar,commons-math*.jar,commons-pool*.jar,jstl.jar,geronimo-spec-jaxrpc*.jar,wsdl4j*.jar,ant.jar,ant-junit*.jar,aspectj*.jar,jmx.jar,h2*.jar,hibernate*.jar,httpclient*.jar,jmx-tools.jar,jta*.jar,log4j*.jar,mail*.jar,slf4j*.jar,xercesImpl.jar,xmlParserAPIs.jar,xml-apis.jar,dnsns.jar,ldapsec.jar,localedata.jar,sunjce_provider.jar,sunmscapi.jar,sunpkcs11.jar,jhall.jar,tools.jar,sunec.jar,zipfs.jar,apple_provider.jar,AppleScriptEngine.jar,CoreAudio.jar,dns_sd.jar,j3daudio.jar,j3dcore.jar,j3dutils.jar,jai_core.jar,jai_codec.jar,mlibwrapper_jai.jar,MRJToolkit.jar,vecmath.jar,junit.jar,junit-*.jar,ant-launcher.jar
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.library.path: /usr/lib/jvm/java-6-openjdk/jre/lib/amd64/server:/usr/lib/jvm/java-6-openjdk/jre/lib/amd64:/usr/lib/jvm/java-6-openjdk/jre/../lib/amd64:/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
2012-05-04 21:03:33,039 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.specification.name: Java Platform API Specification
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.class.version: 50.0
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener sun.management.compiler: HotSpot 64-Bit Tiered Compilers
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener os.version: 3.3.4-2-ARCH
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener user.home: /home/picodotdev
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener catalina.useNaming: true
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener user.timezone: Europe/Madrid
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.awt.printerjob: sun.print.PSPrinterJob
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener file.encoding: UTF-8
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.specification.version: 1.6
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener catalina.home: /home/picodotdev/archivos/apache-tomcat-7.0.27
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.class.path: /home/picodotdev/archivos/apache-tomcat-7.0.27/bin/bootstrap.jar:/home/picodotdev/archivos/apache-tomcat-7.0.27/bin/tomcat-juli.jar
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener user.name: picodotdev
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.naming.factory.initial: org.apache.naming.java.javaURLContextFactory
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener package.definition: sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.vm.specification.version: 1.0
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener sun.java.command: org.apache.catalina.startup.Bootstrap start
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.home: /usr/lib/jvm/java-6-openjdk/jre
2012-05-04 21:03:33,040 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener sun.arch.data.model: 64
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener user.language: es
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.specification.vendor: Sun Microsystems Inc.
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.vm.info: mixed mode
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.version: 1.6.0_24
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.ext.dirs: /usr/lib/jvm/java-6-openjdk/jre/lib/ext:/usr/java/packages/lib/ext
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener sun.boot.class.path: /usr/lib/jvm/java-6-openjdk/jre/lib/resources.jar:/usr/lib/jvm/java-6-openjdk/jre/lib/rt.jar:/usr/lib/jvm/java-6-openjdk/jre/lib/sunrsasign.jar:/usr/lib/jvm/java-6-openjdk/jre/lib/jsse.jar:/usr/lib/jvm/java-6-openjdk/jre/lib/jce.jar:/usr/lib/jvm/java-6-openjdk/jre/lib/charsets.jar:/usr/lib/jvm/java-6-openjdk/jre/lib/netx.jar:/usr/lib/jvm/java-6-openjdk/jre/lib/plugin.jar:/usr/lib/jvm/java-6-openjdk/jre/lib/rhino.jar:/usr/lib/jvm/java-6-openjdk/jre/lib/modules/jdk.boot.jar:/usr/lib/jvm/java-6-openjdk/jre/classes
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener server.loader: 
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.vendor: Sun Microsystems Inc.
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener catalina.base: /home/picodotdev/archivos/apache-tomcat-7.0.27
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener file.separator: /
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java.vendor.url.bug: http://java.sun.com/cgi-bin/bugreport.cgi
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener common.loader: ${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener sun.io.unicode.encoding: UnicodeLittle
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener sun.cpu.endian: little
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener package.access: sun.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener sun.desktop: gnome
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener sun.cpu.isalist: 
2012-05-04 21:03:33,041 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener Propiedades del entorno:
2012-05-04 21:03:33,042 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener Server info: Apache Tomcat/7.0.27
2012-05-04 21:03:33,042 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener Java Servlet API version: 3.0
2012-05-04 21:03:33,107 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener Propiedades de entorno para la aplicacion web:
2012-05-04 21:03:33,107 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java:/comp/env/com: org.apache.naming.NamingContext@49b9ef36
2012-05-04 21:03:33,108 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java:/comp/env/com/censurado: org.apache.naming.NamingContext@4e07e80a
2012-05-04 21:03:33,108 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java:/comp/env/com/censurado/mail: org.apache.naming.NamingContext@6913108b
2012-05-04 21:03:33,108 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java:/comp/env/com/censurado/mail/censurado: javax.mail.Session@62a34b91
2012-05-04 21:03:33,108 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java:/comp/env/com/censurado/jdbc: org.apache.naming.NamingContext@4eb64f2e
2012-05-04 21:03:33,108 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener java:/comp/env/com/censurado/jdbc/censurado: org.apache.tomcat.dbcp.dbcp.BasicDataSource@23087d61
2012-05-04 21:03:33,108 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener Propiedades dinámicas:
2012-05-04 21:03:33,108 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener dinamica.servidor: TOMCAT
2012-05-04 21:03:33,108 INFO                                        es.com.blogspot.elblogdepicodev.misc.ContextListener Iniciando aplicacion con la configuracion del entorno de «desarrollo»

La siguiente clase HttpSessionListener es muy básica pero nos puede permitir conocer si en algún momento se están creando muchas sesiones de usuario y podríamos acceder a más información a través de la interfaz HttpSession que podemos obtener del objeto HttpSessionEvent que recibimos en el listener.

package es.com.blogspot.elblogdepicodev.misc;

import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

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

/**
 * Clase que recibe los eventos de creación y destrucción de las sesiones de los
 * usuarios.
 */
public class SessionListener implements HttpSessionListener {

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

 @Override
 public void sessionCreated(HttpSessionEvent se) {
  logger.info("Sesion creada");
 }

 @Override
 public void sessionDestroyed(HttpSessionEvent se) {
  logger.info("Sesion destruida");
 }
}

Referencia:
[1] Librerías de logging para Java (slf4j, log4j, java.util.logging, logback, MentaLog)-slf4j.html
Configuración de entorno en Java con ayuda de Groovy

viernes, 20 de julio de 2012

Pruebas unitarias con Spock y Mockito

Java
Si estamos convencidos de que las teorías de Extreme Programming y demás metodologías ágiles son útiles probablemente haremos algún tipo de prueba automatizada. Los inicios sueles ser difíciles ya que al principio hacer pruebas automatizadas parece más trabajo a realizar, sin embargo, una vez empezado se obtienen rápidamente una serie de beneficios en forma de errores detectados de forma temprana de manera que producimos software con menos errores, tenemos mayor seguridad de que entregamos algo que funciona y el hecho de vernos obligagos a desarrollar código testable obtenemos a cambio software de mayor calidad.

Herramientas para hacer pruebas hay muchas en la mayoría de los lenguajes de programación. En el mundo Java una de las primeras herramientas diponibles y que aún sigue usándose ampliameten es JUnit, luego vinieron otras como JUnit 4 o TestNG. Spock es la siguiente herramienta que está empezando a usarse en muchos proyectos. El objetivo de Spock no es distinto del de Junit o TestNG pero la forma de definir las pruebas y el uso de Groovy le hacen tener una serie de características muy interesantes:
  • Fácil de aprender: en sus ideas es muy parecido a JUnit y por tanto si conocemos JUnit podremos enpezar rápicamente con Spock.
  • Groovy: es el lenguaje empleado en los casos de prueba, un lenguaje menos «verboso» que Java que produce código más legible y compacto que este. Algunas de sus características le hacen más adecuado que Java para los casos de prueba.
  • Información: en caso de que alguna prueba falle proporciona información detallada de la comprobación que fallo y sus valores.
  • Soporta varias orientaciones: Se adapta a como deseemos abordar las pruebas, probar primero, probar al final, teses unitarios, teses de integración, desarrollo orientado por pruebas, desarrollo orientado por comportamiento.
  • Compatible: Es compatible con JUnit y por tanto se puede integrar con las principales herramientas e IDEs existentes.
Veamos un ejemplo de como son los teses con Spock:

import spock.lang.Specification

class HelloWorldSpock extends Specification {
    def "length of Spock's and his friends' names"() {
        expect:
        name.size() == length

        where:
        name     | length
        "Spock"  | 5
        "Kirk"   | 4
        "Scotty" | 6
    }
}

En este ejemplo se puede ver el DSL (Domain Specific Language) empleado por Spock. La sección expect es el test que queremos realizar, en este caso que una cadena tenga cierta longitud. La sección where son los datos que proporcionamos al test. Este test es un test parametrizado donde la sección expect se evalua para cada conjunto de valores indicado en la sección where.

El test anterior es muy simple y normalmente necesitaremos hacer mocks para los objetos con los que se relaciona el SUT (Subject Under Test) que queremos probar. En el siguiente ejemplo tenemos la interfaz de un servicio que se encarga de buscar datos en un sistema externo como una base de datos o un web service, si no queremos hacer un test de integración con ese sistema externo tendremos que hacer un mock de la interfaz Servicio. También tenemos la clase Procesador que se encarga de realizar una tarea sobre los datos que obtiene de un objeto que implemente la interfaz Servicio. La clase Procesador y su método procesar forma el SUT en este ejemplo.

Para hacer el mock de la interfaz en el ejemplo se usa la librería Mockito que nos permitirá definir el comportamiento que queramos de los mocks. En la sección setup es lo que hacemos, se crea el mock de Servicio, se define que cuando se llame al método findDatos del Servicio se devuelvan unos datos en concreto y se crea el Procesador pasándole la referencia al mock del Servicio para que le devuelva los datos que deseemos. Dado que conocemos los datos que le va a devolver el Servicio al Procesador y lo que debería hacer el Procesador conocemos el resutlado que debería devover el método procesar y esto es la comprobación que se realiza en la sección then donde se comprueba lo esperado con lo que devuelve. La sección when es el estímulo que proporcionamos al SUT con la tarea que queremos que haga y que nos servirá para comprobar que devuelve los resultados esperados. Este ejemplo es muy simple pero muestra los principales conceptos con los que realizar la mayoría de teses unitarios que queramos.

import spock.lang.Specification
import org.mockito.Mockito

class MockitoSpock extends Specification {
    def "procesado de elementos"() {
        setup:
        def servicio = Mockito.mock(Servicio.class)
        Mockito.when(servicio.findDatos()).thenReturn([[id:1, letra:'A'], [id:2, letra:'B']])        
        def procesador = new Procesador(servicio)
            
        when:
        def resultado = procesador.procesar() 
    
        then:
        resultado == '1,2'
    }
}

interface Servicio {

    List findDatos()
}

class Procesador {

    private Servicio servicio

    Procesador(Servicio servicio) {
        this.servicio = servicio
    }

    String procesar() {
        def datos = servicio.findDatos()
        def ids = datos.collect {
            it.id
        }
        return ids.join(',')
    }
}

En la siguientes capturas se puede ver el informe generado por un proyecto gradle que contenga estos teses tanto pasando de forma correcta como incorrecta.



Refencia:
http://code.google.com/p/spock/
http://code.google.com/p/spock/wiki/GettingStarted
http://code.google.com/p/mockito/