sábado, mayo 26, 2007

Nuevo impulso al soporte multimedia en Java

Si hacemos un seguimiento de la lista de Bugs más votados veremos que hay una serie de funcionalidades básicas que llevan muchos años esperando "su oportunidad". Entre ellas está la carencia de soporte multimedia en Java, cualquier persona que haya trabajado/sufrido con el JMF se habrá dado cuenta que hoy en día Java no es una alternativa real a la hora de realizar una aplicación de escritorio con soporte multimedia. El estado del soporte multimedia en Java se puede resumir con los siguientes hechos:
  • la última actualización del JMF se remonta al 2004
  • la ausencia total de mensajes por parte de empleados de Sun en la lista oficial de discusion
  • el número de formatos soportados en su momento era escaso, pero hoy en día roza lo ridículo
  • el instalador del JMF ... en fin me faltan palabras para describirlo
Visto lo visto parece que Sun estos años se ha centrado más en distanciar J2EE de sus competidores que en mantener a Java con cierta dignidad en los mercados de escritorio.

Durante todos estos años han aparecido alternativas que intentaban paliar las carencias del JMF, entre las que podemos encontrar las siguientes:
A pesar de que proyectos como FOBS y JOrbis ofrecen soluciones a algunos problemas concretos del JMF, ninguno ha conseguido que Java se haya mantendido como solución multimedia frente a sistemas como Flash y su formato de vídeo propietario FLV. A mi modo de ver uno de las principales razones es que el código fuente del JMF no se ha liberado con licencia GPL (o similares) en todos estos años, lo que ha hecho que muchos desarrolladores se negaran a desarrollar soluciones libres y gratuitas para un producto propietario. A todo esto hay que unir que la triste realidad es que la mayor parte de los formatos más usados no son libres/gratuitos (salvo honrosas excepciones). Todas estas circustancias han hecho que la comunidad se haya rendido y haya optado por encauzar sus esfuerzos a otros proyectos (podemos encontrar muchos ejemplos).

Después de este horizonte tan negro parece ser que Sun, por fin, va a dedicar recursos al soporte multimedia en Java a través del proyecto Java Media Components. Habrá que estar atento a las noticias de las próximas semanas/meses a ver si es cierto. Puede ser que por fin Java pueda competir en igualdad de condiciones en el escritorio.

Esperemos que no sea demasiado tarde.

Del.icio.us Meneame

martes, diciembre 05, 2006

Distribuir el JMF con nuestras aplicaciones

Programar con las librerías del JMF es un auténtico tormento, hacer que funcionen es más difícil todavía, y realizar el despliegue de estas aplicaciones a gran escala es casi imposible. Normalmente intento evitar usar el JMF, pero muchas veces no queda más remedio. En estos casos intento usar la versión multiplataforma de estas librerías, ya que simplemente es necesario incluir los jars en el CLASSPATH para que funcione.

El número de formatos soportados por la versión multiplataforma del JMF es patético, si bien es cierto que el soporte de las versiones para las Windows y Linux no es mucho mejor, al menos es "algo" mejor. Por esto a veces no queda más remedio que usar este tipo de versiones. Tan sólo queda esperar que el panorama mejore ahora con el cambio de licencia del software de Java.

Si usas el JMF en una aplicación la solución más evidente es indicar al usuario que tiene que instalar el JMF. En el caso de Linux la instalación del JMF en Linux no está al alcance al usuario medio. En el caso de Windows la instalación es mucho más sencilla, pero si el usuario tiene activadas las actualizaciones automáticas de java, cada vez que el usuario actualice su versión java, el JMF dejará de funcionar y tendrá que instalarlo de nuevo. Esta situación normalmente es inadmisible para cualquier persona/empresa que distribuya aplicaciones en Java.

Por último, si te gusta (como a mi) distribuir la aplicación con su propio JRE (para evitar tener que explicarle al usuario que es eso de JAVA), también es deseable integrar el JMF para que el usuario no tenga que instalar ningún paquete adicional.

Integrar la versión multiplataforma del JMF en nuestras aplicaciones no tiene ninguna historia, es una librería empaquetada en un jar como cualquier otra libería en java. El caso de las versiones específicas para cada una de las plataformas es distinto. A continuación voy a comentar mis experiencias a la hora de distribuir el JMF con mis aplicaciones.

Distribuir JMF 2.1.1e (Solaris/Linux Performance Pack) en Linux

Para poder distribuir el JMF con nuestras aplicaciones en Linux, primero tenemos que instalarlo en una máquina con Linux (una opción es usar VMware, si no tenemos ninguna máquina con Linux). Una vez instalado deberemos copiar todo el contenido del directorio lib y el fichero readme.html a un subdirectorio de nuestra aplicación que podemos denominar jmf_linux. El fichero readme.html hay que distribuirlo para cumplir con la licencia de distribución del JMF.

En el fichero de arranque de nuestra aplicación debemos establecer una serie de variables de entorno para que nuestra aplicación reconozca el JMF (todas estas sentencias deberían estar en el script de arranque de la aplicación).

export JMF_HOME="${INSTALL_PATH}/jmf_linux"
export CLASSPATH="${JMF_HOME}/jmf.jar:${JMF_HOME}/mediaplayer.jar:${CLASSPATH}"
export LD_LIBRARY_PATH="${JMF_HOME}:${LD_LIBRARY_PATH}"

Además de estas variables debería estar establecida previamente la variable de entorno $INSTALL_PATH, indicando el directorio de instalación. Posteriormente en el mismo script ejecutaremos nuestra aplicación indicando el classpath correcto, por ejemplo:

java -cp $CLASSPATH:lib/app.jar Aplicacion

Distribuir JMF 2.1.1e (Windows Performance Pack) en Windows
La distribución del JMF en Windows es bastante más complicada que en Linux, ya que en Windows no hay un método sencillo de indicar en donde se encuentran las librerías nativas (por defecto intenta buscarlas en C:\Windows\system32, que es donde las pone el instalador el JMF para Windows). En primer lugar debemos instalar el JMF en un ordenador con Windows, una vez instalado localizaremos el directorio de instalación (normalmente C:\Archivos de Programa\JMF2.1.1e) y copiaremos todos los contenidos del subdirectorio lib y el fichero readme.html en un subdirectorio de nuestra aplicación que llamaremos jmf_windows.

A partir de aquí empieza más complicado, ya que primero deberemos localizar las dlls necesarias para el JMF en el directorio de librerías del sistema (normalmente C:\Windows\system32) y copiar esas dll al directorio raiz de nuestra aplicación. Las dlls que necesitamos son las siguientes: jmacm.dll, jmam.dll, jmcvid.dll, jmdaud.dll, jmdaudc.dll, jmddraw.dll, jmfjawt.dll, jmg723.dll, jmgdi.dll, jmgsm.dll, jmh261.dll, jmh263enc.dll, jmjpeg.dll, jmmci.dll, jmmpa.dll, jmmpegv.dll, jmutil.dll, jmvcm.dll, jmvfw.dll, jmvh263.dll, jsound.dll

La razón por la que debemos copiarlas al directorio raíz de nuestra aplicación es que Windows por defecto también busca las librerías nativas en el directorio de ejecución del programa. Hay que tener cuidado con este detalle, ya que debemos asegurarnos que el directorio de ejecución es siempre ese, incluso cuando se ejecuta desde accesos directos y similares.

Una vez creado el directorio jmf_windows y copiadas las dlls al directorio raiz de nuestra aplicación podremos arrancar nuestra aplicación a través de un fichero .bat, por ejemplo:

java -cp .;lib\app.jar;jmf_windows\jmf.jar;jmf_windows\sound.jar Aplicacion

Crear un instalador multiplataforma

La mejor opción para crear un instalador multiplataforma para aplicaciones en Java es IzPack, este instalador proporciona muchas opciones que podemos consultar en su documentación, pero la que más nos interesa es la posibilidad de indicar paquetes diferentes para diferentes plataformas (en nuestro caso Windows y Linux). Podemos meter en un paquete el núcleo de nuestra aplicación, y en dos paquetes diferentes los dos JMFs, uno para Linux y otro para Windows.

Así en el fichero xml en el que definimos la generación del instalador indicaremos dos paquetes similares a los siguientes:

<pack name="Java Media Framework" required="yes">

<os family="unix" />

<description>Java Media Framework</description>


<fileset dir="jmf_linux" targetdir="$INSTALL_PATH/jmf_linux" />

<file src="aplicacion.sh" targetdir="$INSTALL_PATH" os="unix" />

<executable os="unix" keep="true" failure="warn" stage="never"

targetfile="$INSTALL_PATH/aplicacion.sh" />

</pack>


<pack name="Java Media Framework" required="yes">


<os family="windows" />


<description>Java Media Framework</description>


<fileset dir="jmf_windows" targetdir="$INSTALL_PATH/jmf_windows" />


<fileset dir="jmf_windows_dll" targetdir="$INSTALL_PATH" />


<file src="aplicacion.bat" targetdir="$INSTALL_PATH" os="windows" />


</pack>



De esta forma podremos estar seguros que en cada plataforma estará disponible el JMF apropiado.

Problemas comunes del JMF en Linux
Las aplicaciones el JMF no funcionan en las últimas distribuciones de Linux, ya que con el Toolkit por defecto de java (sun.awt.X11.XTookit) en Linux no funcionan las aplicaciones del JMF. Por esto al ejecutar las aplicaciones jmfregistry y jmfstudio es necesario editar los ficheros y añadir a la línea de ejecución de la aplicación el Toolkit adecuado, por ejemplo en la última línea del jmfregistry:

exec java -Dawt.toolkit=sun.awt.motif.MToolkit JMFRegistry

Para depurar la carga de librerías nativas en Linux disponemos de múltiples métodos, uno de ellos es localizar el pid de nuestra aplicación y revisar el fichero /proc/num_pid/maps para ver si se ha cargado la librería necesaria. Otra opción es establecer la variable de entorno LD_DEBUG y obtendremos información sobre la carga de librerías dinámicas en la salida estándar del programa:

export LD_DEBUG=xxx

Para ver los posibles valores que puede tomar LD_DEBUG podemos revisar la página del manual con "man ld.so". En la misma página del manual se puede ver que con la variable de entorno LD_DEBUG_OUTPUT podemos indicarle un fichero en el que volcar esta información.

También podemos obtener información muy valiosa del log del jmf. Para activar la generación de este fichero de log e indicar el directorio en el que queremos que lo escriba (cuidado con tener permiso de escritura en ese directorio), tenemos que ejecutar la aplicación jmfregistry.

Además de la carga de librerías nativas en Linux, hay otra serie de errores difíciles de depurar en Linux. A menudo tenemos aplicaciones que hacen uso intensivo de Threads y nos funcionan en Windows y en Linux nos encontramos con un desagradable deadlock. Java proporciona un sistema para poder depurar este tipo de errores, ya que si enviamos la señal nº 3 al proceso este mostrará en la salida estándar información sobre los threads y posibles deadlocks. Si nuestra aplicación tiene el pid nº 101 (podemos localizar el pid de la aplicación con el comando ps), podemos hacerlo así:

kill -3 101

Me imagino que muchos de vosotros habreis tenido problemas en la distribución de aplicaciones multiplataforma en Java, me gustaría escuchar vuestras experiencias al respecto a través de comentarios.

Edición: Hay que tener la precaución de poner el jmf.properties con los codecs que necesitamos en el directorio en el que se encuentre el jmf.jar.

Del.icio.us Meneame

lunes, noviembre 13, 2006

Java liberado bajo licencia GPL

Si se confirman los rumores de que hoy va a anunciarse la liberación de parte de Java bajo licencia GPLv2, esta va a ser una de las noticias más importantes y esperadas de los últimos años en lo que se refiere al software en general y al Software Libre en particular.

Con este movimiento Sun quiere abrir nuevos mercados en los que Java estaba vetado debido a su licencia. Habrá que ver como discurren los acontecimientos, pero este cambio probablemente haga que Java recupere el terreno que había perdido en el mundo del software libre en pocos años. Otro hecho a tener en cuenta es que muchas empresas y gobiernos que eran reticentes a incorporar Java debido a su licencia, con este cambio ya no tendán inconveniente en apostar por Java en sus sistemas.

Habrá que estar atento a las reacciones que se van a producir a esta noticia.

Del.icio.us Meneame

miércoles, noviembre 08, 2006

MiG Layout, un Layout Manager imprescindible

Crear una interfaz gráfica compleja con los Layout Managers que trae Java se convierte en una auténtica pesadilla, cualquiera que haya perdido una tarde entera depurando comportamientos extraños de GridBagLayout al redimensionar una ventana sabe de qué estoy hablando. Ya sé que actualmente existen herramientas que facilitan enormemente la creación de interfaces gráficas de forma visual, y no dudo que sean una opción para aplicaciones de tamaño pequeño-mediano, pero para grandes aplicaciones en las que tienes que mantener una coherencia visual a lo largo de la aplicación no queda más remedio que codificar el Layout.

Para evitar tener que usar el GridBagLayout y paneles anidades, hace tiempo que uso como Layout Manager el Forms Layout de JGoodies que facilita enormemente la creación de interfaces gráficas, especialmente los formularios de toma de datos. Existe una estupenda introducción en la web a esta Layout Manager. Esta clase permite trasladar el diseño de una interfaz gráfica del boceto en papel al código de forma sencilla. Otra de las grandes ventajas de este layout manager es que evita anidar varios JPanel, lo que facilita por una parte la lectura del código y por otra la depuración. Además de su facilidad de uso, su licencia poco restrictiva que permite usarlo en proyectos comerciales es otra buena razón para usarlo.

Evidentemente no es la única posibilidad, para los que querais ver varios Layout Managers en acción podeis consultar la página en java.net que permite comparar el código de varios de ellos generando la misma interfaz.

Hace un par de meses a través de un artículo de java.net encontré un nuevo Layout Manager denominado MiG Layout. Después de revisar las funcionalidades que ofrecía me pareció realmente impresionante, el único problema que encontraba es que no aclaraban la licencia de distribución. Esto se ha solucionado en las últimas semanas ya que en su página web han comenzado a distribuir el código con licencia BSD, diciendo que se puede usar para proyectos open source y proyectos comerciales. Entre sus cualidades está que mantiene la simplicidad del Forms Layout, imprescincible para comenzar a usarlo, añadiéndole innumerables funcionalidades que pueden ser muy útiles en layouts complejos. Entre estas nuevas funcionalidades podemos destacar el docking de componentes, la posibilidad de ligar la posición de un componente a la de otro, espacios entre filas/columnas, creación sencilla de barras de botones, etc.

En la página web de MiG Layout se puede encontrar una introducción para los que querais echarle un vistazo rápido, documentación más completa y también una hoja de referencia que facilita las cosas a la hora de programar interfaces gráficas con este layout. En la misma página también ofrecen una demo para swing y otra demo para SWT (efectivamente ... está disponible para ambos toolkits), en la que se pueden ver muchos ejemplos de layouts complejos con su código fuente listos para copiar y adaptar a nuestras necesidades. También se puede encontrar una introducción sobre este Layout Manager en este artículo.

A pesar de ser software bastante reciente si se muestra estable creo que proximamente será una librería imprescincible para cualquier desarrollador de interfaces gráficas en Java.

Me encantaría que comentaseis vuestras impresiones sobre este Layout Manager.

Del.icio.us Meneame

miércoles, octubre 11, 2006

Abrir desde Java aplicación asociada por defecto para un fichero o URL (en Windows/Linux/MacOS)

Cuando programas aplicaciones para entornos de escritorio es común tener que abrir aplicaciones externas que estén asociadas con un determinado tipo de archivos. Es muy común tener que abrir una página web en el navegador, una imagen en el visor de imágenes del sistema operativo, etc. También es una situación bastante común lanzar el reproductor de audio/video del sistema operativo para no depender del JMF para reproducir un archivo multimedia (si alguien ha sufrido el JMF entenderá lo que estoy diciendo).

Esta tarea que a priori debería ser sencilla, en la realidad es una auténtica pesadilla en Java. El problema es que no existe ningún mecanismo estándar para averiguar cual es la aplicación asociada para un determinado tipo de archivo, por lo que normalmente hay que diseñar una solución muy específica que suele hacer que esa aplicación dependa de un programa externo vinculado a un sistema operativo.

La cosa se vuelve más complicada si pretendemos que esta solución sea multiplataforma. No existen muchos proyectos que ofrezcan soluciones generales para este problema, el único que ofrece una solución multiplataforma para este problema es jdic. Este proyecto usa JNI y depende de librerías nativas, lo que complica bastante el despliegue de la aplicación, por lo que a veces no es posible usarlo.

En entornos windows parece bastante arrancar la aplicación asociada a un archivo o URL, ya que existe el comando start que permite arrancar la aplicación asociada a un fichero o url. La única dificultad radica en diferenciar entre Windows 95 y las versiones actuales de Windows para ejecutar el intérprete de comandos adecuado. Existen otras soluciones más elaboradas, como puede ser averigual cuál es la aplicación asociada en el registro, pero creo que simplemente añaden complejidad al asunto y no aportan ninguna mejora.

En el caso de Linux la cosa se complica bastante, ya que no existe ningún registro centralizado de los programas asociados a un tipo de archivo o URL, por lo que cada entorno de escritorio ha solucionado este problema de formas distintas. Existe un subproyecto de FreeDesktop denominado Portland, que dentro de su paquete de utilidades xdg-utils incluye la aplicación xdg-open que permite solucionar el problema en Linux. Actualmente no está integrado dentro de todas las distribuciones, pero cuando lo esté xdg-open será un equivalente en Linux a su homólogo start en Windows.

Actualmente y a la espera de que se incluya el paguete xdg-utils en todas las distribuciones, la mejor opción es intentar detectar el entorno de escritorio que en el que se está ejecutando nuestra aplicación en Java, y en función de esto usar el programa específico para ese entorno de escritorio que nos proporcione la aplicación asociada. En el caso de GNOME tenemos la aplicación gnome-open y en el caso de KDE tenemos kmfclient.

Podemos encontrar un resumen de las opciones disponibles en Windows y Linux en este documento. También podemos ver ejemplos de uso de estas aplicaciones en Java en [1] y [2].

Con toda esta información he intentado crear una clase que recoge todas las ideas antes expuestas y que pretende solucionar el problema en el abanico más amplio de plataformas posible sin depender de librerías o programas externos.

Me gustaría compartir este código (que es tan sólo una primera versión) y que me comentaseis qué os parece y qué cosas cambiaríais/mejoraríais.


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.File;

public class ApplicationLauncher {

public static void main(String args[]){
if(args.length!=1) {
System.out.println("Número de parámetros erróneo forma de uso: java -cp . ApplicationLauncher fichero|URL");
return;
}
if(args[0].indexOf("http")!=-1) launchURL(args[0]);
else launchDefaultViewer(args[0]);
}

private static String linuxDesktop = null;

private static String getEnv(String envvar){
try{
Process p = Runtime.getRuntime().exec("/bin/sh echo $"+envvar);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String value = br.readLine();
if(value==null) return "";
else return value.trim();
}
catch(Exception error){
return "";
}
}

private static String getLinuxDesktop(){
//sólo se averigua el entorno de escritorio una vez, después se almacena en la variable estática
if(linuxDesktop!=null) return linuxDesktop;
if(!getEnv("KDE_FULL_SESSION").equals("") || !getEnv("KDE_MULTIHEAD").equals("")){
linuxDesktop="kde";
}
else if(!getEnv("GNOME_DESKTOP_SESSION_ID").equals("") || !getEnv("GNOME_KEYRING_SOCKET").equals("")){
linuxDesktop="gnome";
}
else linuxDesktop="";

return linuxDesktop;
}

public static Process launchURL(String url){
try{
if (System.getProperty("os.name").toUpperCase().indexOf("95") != -1)
return Runtime.getRuntime().exec( new String[]{"command.com", "/C", "start", url} );
if (System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") != -1)
return Runtime.getRuntime().exec( new String[]{"cmd.exe", "/C", "start", url} );
if (System.getProperty("os.name").toUpperCase().indexOf("MAC") != -1)
return Runtime.getRuntime().exec( new String[]{"open", url} );
if (System.getProperty("os.name").toUpperCase().indexOf("LINUX") != -1 ) {
if(getLinuxDesktop().equals("kde"))
return Runtime.getRuntime().exec( new String[]{"kfmclient", "exec", url} );
else
return Runtime.getRuntime().exec( new String[]{"gnome-open", url} );
}
}
catch(IOException ioex){System.out.println(ioex);}

return null;
}

public static Process launchDefaultViewer(String filepath){
return launchURL( new File(filepath).getAbsolutePath());
}

}

Del.icio.us Meneame

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