sábado, julio 15, 2006

Java Studio Creator 2 y Tomcat 5.5

Uno de los mejores IDE para JSF es el nuevo Java Studio Creator 2 (JSC). Es muy fácil
crear aplicaciónes con una arquitectura muy similar a swing. JSC trae el Java
System Application Server 8.x Platform Edition para las pruebas durante el desarrollo.
Pero usualmente utilizo el Tomcat, pasé una tarde completa tratando de hace deploy
y me encontraba con este error.
Cannot create JDBC driver of class '' for connect URL 'null'
Seguí todas las instrucciones que mencionan en la página de Sun.
  1. Exportar el proyecto como War para JDK 1.4.1
  2. Incorporar las librerías jstl.jar y standard.jar JSTL 1.1
  3. Configurar el JNDI Datasource en Tomcat 5.5 (how-to)
  4. Si estamos usando MySQL u otro DBMS copiar su correspondiente jdbc driver o connector en /commons/lib
  5. Listo! es todo y ahora a probarlo en http://localhost:8080/app-name
Pues parece sencillo, pero el punto 3 tiene sus truquitos.

Imaginemos que tenemos una BD de nombre lamparas con un usuario lamparas y password lamparasdata. En el server.xml de tomcat el JDBC quedaría así:

<Resource name="jdbc/lamparas" auth="Container"
type="javax.sql.DataSource" username="lamparas" password="lamparasdata"
removeAbandoned="true" logAbandoned="true"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost/lamparas?autoReconnect=true"
maxActive="8" maxIdle="4" />

Luego copiaríamos el WAR a al directorio /webapps
Tomcat lo descomprime y lo instala, nos aseguramos que en el web.xml (web descriptor) se encuentre la siguiente definición. El web.xml estará en
...
<resource-ref>
<description>Creator generated DataSource Reference</description>
<res-ref-name>jdbc/lamparas</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
...
Arrancamos la aplicación y funcionará normalmente hasta que algún componente que haga binding a un elemento de la BD se presente y entonces nos mostrará el error anterior.

Después de muchas horas de intentos fallidos, Saurondor me sugirió incluir en un Context la configuración del jdbc en server.xml yo le comenté que no me parecía la solución correcta pues entonces perdería toda practicidad hacer deploy con el WAR si de todas formas tengo que modificar el server.xml (que ya lo hice una vez, ¿no?). Para variar los errores con JSF son crípticos y en realidad no sabes lo que está pasando, pero parece ser que no encuentra la definicion del Datasource en server.xml por que le regresa valores nulos, con la configuración de arriba supuestamente la definición es global, pero no la lee.

Sin más que intentar y apunto del hartazgo le puse los tags de Context y sus parámetros. Lo intenté así y funcionó, respiré profundo y me dije que no volvería a desechar algo que parezca obvio.

La configuración final quedó así:

<Context path="/lamparasweb" docBase="lamparasweb" debug="5" reloadable="true"
crossContext="true">
<Resource name="jdbc/lamparas" auth="Container"
type="javax.sql.DataSource" username="lamparas" password="lamparasdata"
removeAbandoned="true" logAbandoned="true"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost/lamparas?autoReconnect=true" maxActive="8" maxIdle="4" />
</Context>

*Actualización 5 mayo 2007
Vicente David comenta que podemos incluir esta información dentro de nuestro proyecto. Esto es muy útil cuando no tenemos acceso a los archivos de configuración del server logrando así una independencia en la configuración.

Con sólo incluir los datos dentro de un archivo llamado context.xml en META-INF. META-INF es un directorio con información de la aplicación que se lee cuando el WAR está siendo descomprimido. Es decir, sólo funcionará este método cuando estemos desplegando la applicación desde un WAR.

META-INF/context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/lamparas" auth="Container"
type="javax.sql.DataSource" username="lamparas"
password="lamparasdata" removeAbandoned="true" logAbandoned="true"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost/lamparas?autoReconnect=true" maxActive="8"
maxIdle="4" />
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>META-INF/context.xml</WatchedResource>
</Context>

16 comentarios:

Unknown dijo...

Hola, estoy intentando probar una aplicacion desarrollada en el ide jsc, es una simple pagina de hola mungo, pero al exportarla como war y querer ejecutarla en el tomcat. siguiendo los pasos que mencionas. el explorer no lo interpreta. si alguien me puede ayudar. gracias.

juparave dijo...

¿Cuál es el mensaje que te manda?
de conexión... de servidor... de applicación?

El error más común es la disposición de las librerías. jstl.jar y standard.jar pueden estar en tu WEB-INF/lib o en {catalina_home}/common/lib

Sentei dijo...

Hola amigo muy buena explicacion si la hubiera encontrado antes ... bueno para felicitarte pero a la ves quiero dar un pequeño aporte ... para no tener que estar abriendo el server.xml y modificandolo manualmente ( es un poco incomodo verad ) bueno lo que hay que hacer es en dentro de la carpeta web de tu proyecto crear la carpeta META-INF y dentro de ella el archivo context.xml
y el contenido de la misma es el mismo que pusistes dentro de el server.xml , pero al deployar no se va a copiar dentro de server.xml ... no te asustes lo copiara en $CATALINA_HOME\conf\Catalina\localhost\nombre_de_tu_proyecto otro lugar pero igual tendras acceso a los rescursos... yo creo que es una manera manera mas ordenada ya que cuando quieras agregar un recurso a una aplicacion modificas unicamente ese recursos y no esta todo acumulado en el server.xml bueno un gusto hasta luego

juparave dijo...

Hola Vicente, tienes razón en tu pequeño aporte, podemos incluirlo en un archivo META-INF/contex.xml sólo hay que hacer la anotación de sólo funcionará si el despliege de la aplicación lo haces con un WAR, de lo contrario TOMCAT no hará caso del ese archivo, pues sólo lee el directorio META-INF cuando está haciendo la descompresión del WAR.

Ingeweb.es Soluciones Informáticas dijo...

Hola, que bueno haber encontrado esta página, porque estoy desesperada y los de soporte del alojamiento de mi página no me dan solución. Tengo un pool de conexiones a MySQL con Tomcat 5.5.x como decís y cuando arranco yo manualmente el Context.xml funciona la conexión, pero al cabo de unas horas que no se realice ninguna conexión, muy común por la noche, la conexión se pierde y no vuelve a reconectar. Y debe ser porque el MySQL del servicio de Hosting está así configurado y que pase de mi Context.xml (puesto que indico autoReconect=true en la URL), como decis vosotros.

Si esto es así, no sé como solucionarlo, si pudiera configurar algo en mi espacio en de Hosting para que se ejecuta, porque si dependo del servidor compartido... Otra cosa que había pensado como última solución es lanzar una tarea programada cada x horas que lea por ejemplo un campo de la base de datos para que no se pierda, aunque no tengo ni idea de cómo se haría esto. Aunque así lo solucionara siempre quedaría el problema cuando reinicien los del Hosting el servidor compartido.
Muchas gracias por adelantado y saludos.

juparave dijo...

MySQL tiene un timeout para conexiones de 8 horas. Si tu conexión se queda inactiva más de ese tiempo se cierra.

A lo mejor no es necesario que que tu servicio de hosting haga algún cambio.

Ya para MySQL 5 hay unos parámetros recomendados para dejar de usar el autoReconnect.

Resumido:
validationQuery="SELECT 1"
testWhileIdle="true"
timeBetweenEvictionRunsMillis="10000"
minEvictableIdleTimeMillis="60000"

Puedes ver más detalles en
http://dev.mysql.com/doc/refman/5.0/es/connector-j-usagenotes-j2ee.html

pruébalo y dinos si te funcionó

Ingeweb.es Soluciones Informáticas dijo...

Hola, he probado vuestra solución pero sigue sin funcionar.
Dado que va a resultar difícil que hagan algún cambio en el alojamiento, lo que se me ocurre es tener un proceso programado para que acceda a base de datos cada 7:59 horas, p. ej., sabeis si esto es posible?
Gracias y saludos.

juparave dijo...

Quizás podrías poner un ContextListener que arranque cada vez que tu aplicación lo haga y dentro de este tengas un Timer. Algo así...

static class TimerThread
extends Thread
{
TimerThread()
{
this.setDaemon(true);
}

public void run()
{
while (true) {
try {
sleep(7 * 60 * 60 * 1000); // cada siete horas
algunQuery();

} catch (Throwable e) {
// error por algo
}
}
}

Claro que esta no es la mejor solución a tu problema y lo recomendable sería que tu ISP te ayudara.

¿puedes poner tus archivos de configuración?

Ingeweb.es Soluciones Informáticas dijo...

En el context.xml tengo:
url="jdbc:mysql://localhost/xxx" maxActive="100"
maxIdle="30"
maxWait="-1"
removeAbandoned="true"
removeAbandonedTimeout="300"
logAbandoned="true"

Tengo un SevletContextListener que utilizo para abrir y cerrar la conexión.

Gracias y saludos.

Ingeweb.es Soluciones Informáticas dijo...

Se me olvidaba, más las líneas que me dijisteis, lo que pasa que este es el context.xml de mi equipo, y lo otro lo modifique directamente en el servidor compartido.

juparave dijo...

url="jdbc:mysql://localhost/xxx?autoReconnect=true" maxActive="10"
maxIdle="5"

No le pongas tantos en maxActive, tenía 100, ponle 10, lo mismo para maxIdle, de verdad no es necesario tanto.

No veo que le hayas puesto ?autoReconnect=true en tu url, ¿podrías porbarlo así?

El resto parace bien

Ingeweb.es Soluciones Informáticas dijo...

Hola, he probado todo lo que me has dicho pero se sigue quedando sin conexión.

Saludos.

juparave dijo...

He realizado varias pruebas y no consigo el error de desconexión. Está raro

Ingeweb.es Soluciones Informáticas dijo...

Hola, perdona mi tardanza pero he estado de vacaciones.
Y tan raro que podía ser el error, resulta que creo que es culpa mía por marear el código y es que en tenía en el ContextListener:

public void contextDestroyed(ServletContextEvent servletContextEvent) {
ServletContext servletContext = servletContextEvent.getServletContext();
PortalWebBD portalWebBD = (PortalWebBD) servletContext.getAttribute("BDPortalWeb");
portalWebBD.close();
servletContext.removeAttribute("BDUsuarios");
}

Es decir, que no estaba quitando el atributo que creaba para la conexión en el ContextDestroyed (está su antiguo nombre). Plas, plas, plas, pa mi, me he dado cuenta cuando te iba a hacer copy+paste para saber si lo estaba haciendo mál, jejeje.
Lo he corregido y he puesto:
servletContext.removeAttribute("BDPortalWeb");
Así que ya la prueba del algodón la acabo de hacer, he subido la clase, parado y vuelto a arrancar el Tomcat, así que dentro de 8 horas lo sabré si ya no se pierde. Mañana te cuento, muchas gracias y perdona si ha sido por mia culpa.

juparave dijo...

Que bueno que le encontraste, muchas veces tenemos errores de ese tipo por que estábamos probando algo, lo dejamos para después, se nos olvida y seguimos avanzando en el proyecto.

Estoy casi seguro que no te fallará ahora, saludos y buena suerte.

Ingeweb.es Soluciones Informáticas dijo...

Hola, no debia ser eso pues se sigue perdiendo la conexión. He enviado un mail al soporte del servidor compartido para ver que está sucediendo.

De todas formas, mientras tanto he pensado en aplicar la solución del thread que me dijiste, pero el problema es ¿cómo accedo para hacer la query a la variable de contexto donde tengo el atributo de base de datos?.

Muchas gracias y saludos.