lunes, septiembre 25, 2006

Prueba de aplicaciones con VMware y Eclipse

Una de las tareas más tediosas y en las que se pierde más tiempo dentro del proceso de desarrollo de software es la prueba de aplicaciones multiplataforma. Si bien Java simplifica bastante el proceso ya que es un lenguaje multiplataforma, sobre todo en las aplicaciones de escritorio, existen aplicaciones en las que hay que usar funcionalidades que no son multiplataforma como pueden ser el uso de JNI, ejecución de programas o scripts externos, etc.

Hasta hace bien poco la única opción era tener varios ordenadores con distintos sistemas operativos conectados en red (o peor incluso, un sólo ordenador con multiarranque), e ir trasladando la aplicación entre estos ordenadores para ir probándola en todos los sitemas operativos (y todas las versiones) para las que se iba a dar soporte. Esto además del coste que supone tener diversos ordenadores hay que tener en cuenta la cantidad de tiempo que se perdía para mantener toda esta infraestructura de pruebas.

La versión gratuita de VMware es una de las opciones que existen para solucionar una buena parte de estos problemas, ya que nos permite tener en un mismo ordenador todos los sistemas operativos que queramos. Además nos permite mantener copias "limpias" de los sistemas operativos que nos permiten comenzar con el sistema operativo vacío simplemente copiando y borrando máquinas virtuales. El único inconveniente es la copia de ficheros entre todos estos sistemas operativos, pero una solución que suele ser válida para casi todas las soluciones es montar un servidor de FTP o un servidor Web para compartir los archivos entre las distintas máquinas virtuales.

Este sistema ha facilitado mucho la prueba de aplicaciones, pero obliga a instalar un depurador en cada una de las máquinas virtuales, o en su defecto a mostrar muchos mensajes de depuración que permitan localizar los fallos. Para facilitar aún más las cosas podemos usar la opción de "Remote Debugging" del Eclipse.

En mi caso tengo un ordenador en el que tengo instalado el Eclipse y que uso como entorno de desarrollo. En este mismo ordenador he generado varias máquinas virtuales (Windows y Ubuntu entre otras) en las que tengo configurada la red de forma adecuada para poder descargarme la aplicación desde un servidor web apache que tengo instalado. Para depurar de forma remota una aplicación con el Eclipse basta con arrancar las máquinas virtuales, descargar las aplicaciones y arrancarlas añadiendo en la línea de ejecución de las aplicaciones las siguientes opciones:
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=1044
El puerto que ponemos (en este caso 1044) se puede modificar por otro, siempre y cuando pongamos este mismo puerto en la configuración del Eclipse. Es interesante saber el significado del resto de las opciones, pero en la mayor parte de los casos los valores por defecto serán suficientes.

Necesitamos tener acceso por red a las máquinas virtuales para poder conectarnos desde el eclipse a estas aplicaciones y poder depurarlas. Esto no suele ser problema ya que VMware dispone de diversos modos de red que suelen adaptarse a cualquier entorno.

Una vez arrancadas las aplicaciones en las máquinas virtuales, basta con tener un proyecto abierto con las fuentes de la aplicación y hacer un "Debug ... Remote Java Application", para conectarnos y depurar de forma remota las aplicaciones. Hay que tener cuidado de indicar correctamente el host y el puerto en el que se están ejecutando las aplicaciones. Una vez iniciada la depuración veremos como podemos establecer breakpoints y suspender la ejecución de nuestras aplicaciones.

Con este sistema es muy sencillo depurar aplicaciones en diversos sistemas operativos con una infraestructura mínima (con un sólo ordenador puede ser más que suficiente). Con este sistema casi toda la dificultad radica en la configuración de la red para poder conectar nuestras máquinas virtuales con el sistema anfitrión y así distribuir de forma sencilla los archivos en los que está compuesta nuestra aplicación, normalmente con un servidor web suele ser suficiente para que las máquinas virtuales puedan descargar archivos.

Del.icio.us Meneame

miércoles, septiembre 20, 2006

BeanShell y JDBC

Pensaba que iba a ser muy sencillo conectarme a una base de datos mediante JDBC con BeanShell, simplemente tenía que cargar el driver con addClassPath() ... hasta que me encontré con java.sql.DriverManager.

Después de perder un buen rato intentando conectarme a una base de datos a través de JDBC en la consola de BeanShell pude comprobar que no era tan fácil como había pensado. El código que introducía era el siguiente (intentaba probar parte de un tutorial sobre javadb de java.sun.com):

addClassPath("./derby.jar");
browseClass("org.apache.derby.jdbc.EmbeddedDriver");
driver = getClass("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
import java.sql.*;
String userHomeDir = System.getProperty("user.home", ".");
String systemDir = userHomeDir + "/.addressbook";
System.setProperty("derby.system.home", systemDir);
DriverManager.getConnection("jdbc:derby:DefaultAddressBook;create=true");

// Error: // Uncaught Exception: Method Invocation DriverManager.getConnection : at Line: 1 : in file: : DriverManager .getConnection ( "jdbc:derby:DefaultAddressBook;create=true" )

Target exception: java.sql.SQLException: No suitable driver

Maldita excepción diciendo que no existía dicho driver. A primera vista sorprendente, porque había cargado el driver (el de la base de datos de apache Derby). Pero después de revisar la documentación del BeanShell y el código fuente de la clase java.sql.DriverManager encontré la respuesta.

La clave está en que BeanShell carga las librerías de sistema con el System ClassLoader, y el resto de las clases que se añaden con addClassPath() se cargan con otro ClassLoader propio. Eso explica por qué uso getClass()para registrar el Driver JDBC y no el típico Class.forName(). Voy a explicar esto último un poco mejor, recordemos que el Class.forName(), que usa el ClassLoader que cargó la clase Class (valga la redundancia) para instanciar los objetos que le indiquemos. Por lo tanto este método no va a ser capaz de instanciar objetos del Driver JDBC, ya que estos han sido cargados con el ClassLoader del BeanShell (recordemos que han sido añadidos con addClassPath). Por lo tanto la única solución es usar getClass() que usa el ClassLoader del BeanShell y por lo tanto tiene acceso a las clases JDBC que acabamos de cargar. Es un verdadero lío, lo sé, pero todo lo relacionado con los ClassLoaders en Java es siempre así de complicado.

Lo que no me explicaba es por qué una vez que había conseguido instanciar el driver JDBC seguía sin funcionar el condenado getConnection(). Recordemos que al instanciar un driver, la clase del driver automáticamente se encarga de registrarse en el DriverManager para así poder realizar las conexiones que sean del tipo de ese driver, en este caso de tipo jdbc:derby. Para lograr entenderlo no me quedó mas remedio que ver el código fuente del DriverManager, y como siempre ahí estaba la respuesta. Resulta que el método getConnection() para obtener el driver JDBC mantiene un array de Strings con los nombres de las clases que implementan los distintos tipos de drivers JDBC, y posteriormente intenta instanciarlos usando ... Class.forName(). Volvemos a tener el mismo problema con los ClassLoaders.

Así que no hay solución, no podemos usar el DriverManager para obtener las conexiones, ya que no hay forma de indicarle que use otro ClassLoader para cargar algunos drivers. No queda más remedio que abrir las conexiones a mano.

conn = driver.connect("jdbc:derby:DefaultAddressBook;create=true",System.getProperties());
stmt = conn.createStatement();


No sé si a alguien se le ocurre otra solución que no pase por arrancar el BeanShell con el jar de Derby incluido en el classpath.

Del.icio.us Meneame