Mar 30

Truquillo para reiniciar un war en tomcat

Una vez desplegado nuestro.war, a veces tocamos alguna cosa a mano dentro de los ficheros de nuestra aplicación webapps/nuestro/, quizás un fichero .properties, quizás cambiar un jar por una versión más moderna, y queremos reiniciar la aplicación.

Las opciones más conocidas y estándar son:

  • Entrar en el manager de tomcat y desde ahí pararla y volver a arrancarla
  • La opción bestia, parar tomcat y volverlo a arrancar

Sin embargo, hay otra menos conocida y muy útil si no tenemos el manager instalado (por ejemplo, caso de liferay). Si estamos en linux, basta hacer un touch webapss/nuestro/WEB-INF/web.xml Esto hará que se reinicie la aplicación.

Mar 17

Web Services CXF: Errores de versión con jaxb-api

Si tenemos java 6 y estamos haciendo web services, con apache CXF por ejemplo, y obtenemos errores extraños con la versión de Jaxb-api, como estos

Exception in thread "main" java.lang.LinkageError: JAXB 2.0 API is being loaded
from the bootstrap classloader, but this RI (from jar:file:/D:/work/jaxws-ri/lib/jaxb-impl.jar!
/com/sun/xml/bind/v2/model/impl/ModelBuilder.class) needs 2.1 API.
Use the endorsed directory mechanism to place jaxb-api.jar

 

es que estamos teniendo problemas con la versión de determinadas librerías. El JDK tiene incluidas la versión 2.0 de las librerías JAXB 2.0 y JAX-WS 2.0, mientras que nuestra versión de Web Services (apache CXF por ejemplo), necesita la 2.1 (según la excepción). La forma de solucionar esto es poner las librerías JAX-WS 2.1 y JAXB 2.1 en algún sitio de forma que se encuentren antes que las de defecto del JDK.

Si nuestra aplicación es una aplicación de escritorio, debemos colocarlas en los directorios JDK_HOME/lib/endorsed o JDK_HOME/jre/lib/endorsed, según qué estemos usando.

Si nuestra aplicación corre en un contenedor web de aplicaciones, como Apache Tomcat, debemos colocarla en el sitio que nuestro servidor web tenga indicado para ello. En el caso de Tomcat, sería en CATALINA_HOME/endorsed (debemos crear el directorio si no lo está).

Existe también forma de indicarle a java dónde están los directorios "endorsed" en vez de usar los de defecto, de forma que no tendremos que tocar los directorios de instalación de Java. Podremos así meter esos dos jar en un directorio cualquiera en nuestro ordenador e indicarle a Java en el momento de arrancar la máquina virtual, que ese directorio hace de "endorsed"

java -Djava.endorsed.dirs=UN_DIRECTORIO_CUALQUIERA …

 

que sería válido tanto para aplicaciones de escritorio como para Tomcat.

 Referencias:

http://www.java.net/external?url=http://java.sun.com/j2se/1.5.0/docs/guide/standards/

http://weblogs.java.net/blog/ramapulavarthi/archive/2007/01/problems_using.html

Jan 14

El WebappClassLoader y ContextClassLoader en Tomcat/Liferay

En el desarrollo nos hemos encontrado con un problema curioso. Estamos desarrollando un portlet con Liferay y necesitamos desde él hacer llamadas AJAX que actualicen parte del portlet sin refrescar la página. Se nos ha ocurrido, en la parte del servidor, hacer un jsp que sería el que reciba estas llamadas desde la parte de javascript, digamos un funcion.jsp. En esa funcion.jsp usamos clases de nuestra parte del servidor del portlet, en concreto, usamos los jar que hay en el WEB-INF/lib de ese portlet.

Pues bien, el problema que se nos ha presentado es que aunque todo parece ir bien tanto en el funcion.jsp como en las clases que se van llamando, hay un sitio concreto en el que hay un problema. Esas clases crean un cliente de web service con Apache CXF para conseguir datos en otros servidores y en la creación de ese cliente nos salta el problema, una excepción fea

ClassCastException: com.sun.xml.ws.client.sei.SEIStub cannot be cast to org.apache.cxf.frontend.ClientProxy [java] at org.apache.cxf.frontend.ClientProxy.

A poco que se hurge con google, encontramos que la versión 6 de java viene con un proveedor por defecto para web services. Java proporciona mecanismos para cambiar ese proveedor por defecto por el que queramos, en concreto y entre otras opciones, basta con poner en el classpath el fichero META-INF/services/javax.xml.ws.spi con una única línea de texto que sería la clase proveedor de web service que queramos uar,  en mi caso, la de apache cxf. La excepción surge si cuando java busca el proveedor no lo encuentra, poniendo entonces el suyo por defecto, que no es compatible con Apache CXF.

Normalmente no debemos preocuparnos de esto, el jar cxf-rt-frontend-jaxws.jar de apache cxf contiene ese fichero debidamente configurado, por lo que si este jar está en el classpath, no debería haber problemas.

¿Por qué entonces surge el problema cuando llamo al funcion.jsp si este jar está en el WEB-INF/lib?. Pues tras varios días de pruebas, google y más pruebas, encontramos que nuestras clases usan por defecto el WebappClassLoader que les proporciona tomcat, por lo que todos los jar en WEB-INF/lib de nuestro portlet están disponibles, pero java, cuando busca el proveedor de servicios, usa Thread.currentThread().getContextClassLoader() que NO le devuelve el WebappClassLoader, sino el de la propia aplicación de Tomcat, por lo que las clases de nuestra aplicación no están disponibles.

Si experimentamos y hurgamos un poco más, vemos que Tomcat, cuando va a llamar a algo de nuestra aplicación web, nos cambia ese ContextClassLoader haciendo algo como

Thread.currentThread.setContextClassLoader(elWebappContextClassLoaderCorrespondiente);

y luego, cuando nuestra aplicación termina de hacer lo que tenga que hacer, Tomcat restaura el ContextClassLoader por el de defecto.

El problema que tenemos entonces es que cuando se llama a nuestra funcion.jsp, no se está cambiando ese ClassLoader. Ahora, sabiendo el problema, la solución debería ser fácil.

La primera y más inmediata es que nosotros mismos, antes de crear el cliente web service, cambiamos ese ContextClassLoader y luego lo restauramos, algo similar a lo que hace Tomcat. Esta opción no me gusta porque no me gusta jugar con los ClassLoader sin saber a ciencia cierta qué hago, y sobre todo porque Tomcat debería cambiarnos el ClassLoader y no lo hace, lo que implica que algo tenemos mal configurado.

Como estamos con liferay y llamamos a funcion.jsp con una URL directa, no a través del portlet liferay, imagino que ahí vienen los problemas. Así que toca "investigar" un poco a ver cómo conseguir que liferay/tomcat consideren funcion.jsp como parte del webapp o portlet y le cambien el classloader al Thread dichoso.

 

Nov 10

Pequeño problema con cliente CXF de Web Service

Estoy haciendo un cliente de Web Service con CXF que luego ira en una página web, de forma que un usuario a través de la página pueda consultar los web services. Y ha surgido un pequeño incordio.

Cojo el wsdl y con la herramienta wsdl2java de CXF genero las clases correspondientes al ciente, todo bien. Hago mi software y todo maravilloso, en local, incluso desplegado en mi propio Tomcat, todo va de perlas.

Lo subo al servidor real….. y error. No encuentra los wsdl. Efectivamente, wsdl2java genera el código del cliente y pone en ese código java la ubicación del wsdl. Luego, cuando usas el cliente, el código java de CXF busca no sé para qué ese wsdl y si no lo encuentra, da error y no funciona nada más.

Yo, en mi ignorancia, pensé que ese wsdl no servía para nada una vez generado el código java, así que no lo incluí como parte de mi proyecto ni en el war. Bueno, la solución parece sencilla, basta meterlo en el war y modifiar las clases de cliente generadas por CXF para que lo busquen dentro del webapp en algún sitio. Pues no, no es tan sencillo. Desgraciadamente el código java dentro de un war tiene muy dificil, si no imposible, saber cual es el directorio en el que está la aplicación web. El directorio por defecto suele ser el del ejecutable tomcat y no el de la aplicación web, por lo que una de las soluciones más socorrida en poner los ficheros que necesitas en el classpath, bien dentro de un jar, bien en el directorio WEB-INF/classes. Pero CXF no entiende de una URL que empiece por "classpath:…."

Por internet buscas y sí, hay cosas, pero normalmente suelen ser si usas spring y en los ficheros de XML de configuración de Spring pones cosas y tal. No es el caso, sólo uso Tomcat.

Total, que muy a mi pesar y por no perder el tiempo, opté por poner los wsdl en un directorio fijo y conocido en el servidor y acceder a ellos con path absoluto. En fin, un asquito y una cosa que se me queda en la lista de tareas para revisar "algún día".

ACTUALIZACIÓN: serhii se ha cargado el problema (lo ha solucionado y ya no es un problema), ver comentario número 7. Está claro que algunos debemos dedicarnos a otra cosa 😉  Muchas gracias, serhii.

Mar 25

Subir ficheros al servidor con JSP

 Dentro de la aplicación web que estamos haciendo, tenemos que permitir al usuario subir ficheros al servidor. Sé cómo se suben ficheros al servidor en PHP, pero nunca lo había hecho con JSP. Pensé que sería similar, pero me he encontrado con algunas sorpresas.

La primera sorpresa ha sido buscando en google. Unas búsquedas rápidas no me ha dado ningún resultado en el que se suba el fichero sin necesidad de librerías adicionales. Pensé que sería igual que con Apache/PHP, el servidor sube el fichero a una carpeta temporal y una variable le indica a nuestra aplicación PHP dónde está. En Tomcat/JSP parece ser que no es así. Insisto, quizás sí, pero no he buscado en profundidad.

Todos los resultados de google que he visto hacen referencia a que hay que usar alguna librería externa, como apache-commons-fileupload. Así que me puse a ello, usando esa librería.

La segunda pega es que al ser el form de html enctype="multipart/form-data", necesario para poder subir un fichero, en la parte del servidor JSP dejan de funcionar los request.getParameter(), siempre devuelve null. Resulta que si el request es multipart/form-data, hay que tratarlo de otra manera. Este asunto me ha llamado la atención y le ha hecho perder un puntito a JSP frente a PHP (o a Tomcat frente a Apache, no sé quién es el culpable), donde parece que  no hay esos problemas.

¿Y cómo se leen entonces los parámetros de la petición?. Pues nuevamente una búsqueda rápida en google parece indicar que la única solución es usar librerías externas y en concreto, la misma apache-commons-fileupload. Con esa librería se "parsea" la petición y obtenemos una lista de FileItem. Cada uno de ellos puede ser un fichero al que se ha hecho upload…. ¡¡ o uno de los parámetros !!. Llamando a los método getFieldName() y getString() (comprobando previamente si es parámetro o fichero) de esos FileItem obtenemos los valores.

En fin, algo que me ha parecido rebuscado y demasiado complejo frente a cómo se hace en PHP/Apache. Un pequeño tutorial de esto en la chuwiki: File Upload con JSP.

May 25

Sábado entretenido

 

Ayer, sábado lluvioso, encerrado en casa, decidí entretenerme con el ordenador. Al final, es curioso cómo pasas la tarde, yendo de un lado a otro, sin hacer nada concreto, pero aprendiendo un montón. Ahí va mi pequeña historia del sábado y cómo se van encadenando las cosas.

  • Tenía en mente una pequeña aplicación que me podía resultar de utilidad en el trabajo. Por supuesto la aplicación debía ser web y la iba a hacer en JSP. Me instalé Tomcat en Ubuntu e hice unas pruebas para ver que estaba bien instalado.
  • Me cree con maven y eclipse mi proyecto web con JSP para empezar a trabajar.
  • Mi aplicación va contra base de datos. Por cosas que había leído, me daba la impresión de que Tomcat puede gestionar las conexiones a base de datos con un pool de conexiones, liberando a la aplicación de tener que abrirse sus propias conexiones, así que me pongo a investigar el tema.
  • Encuentro tutoriales, hago todo lo que se supone que hay que hacer, pero no me funciona. Siempre obtengo una excepción de acceso denegado, no hay permisos. Me pongo a investigar cómo gestiona Tomcat el tema de permisos y descubro el fichero catalina.policy.
  • Por supuesto, no es tan fácil como tocar ese fichero. Tomcat lo genera automáticamente en el arranque machacando los cambios que hayas hecho, así que hay que buscar y tocar los ficheros que usa Tomcat para generar ese catalina.policy. Y por supuesto me doy cuenta de ese detalle después de tres o cuatro intentonas fallidas de tocar catalina.policy directamente.
  • Una vez que funciona todo, como no encontré ningún tutorial que diga como configurar el pool de conexiones y además te advierta del tema de permisos, me decido a escribir el mio propio en la wiki: Configurar un DataSource y dar permisos en Tomcat.
  • Al escribir el tutorial, además de explicar cómo se tocan los ficheros a mano, pongo que se puede hacer con el navegador si tenemos instalada la aplicación de administración de Tomcat. Se me ocurre poner una foto del panel de administración, pero pensando un poco, decido que queda más "guay" un video, así podría subir mi video a youtube. Pero claro, hay que capturar el video del escritorio.
  • Me pongo a buscar aplicaciones que capturen video del escritorio en Ubuntu. Encuentro un par de ellas y las pruebo. No me van bien. Al final, una de ellas graba bien pero si la lanzas desde línea de comandos, no desde la interface gráfica de usuario. Al arrancarla desde línea de comandos, en el video capturado se ve la ventana de comandos donde arranco la aplicación, cómo la oculto, cómo la vuelvo a visualizar y cómo paro la grabación.
  • Eso no es bonito, así que decido buscar un editor de video para cortar esos cachos. Encuentro uno pero …. no lee el formato .ogg ni .ogm que es en el que graba la aplicación anterior. Sospecha gorda. ¿admitirá youtube este formato?. Por supuesto, no. Hay que buscar un conversor de formatos.
  • Me pongo a ello, encuentro varios que no me funcionan bien o no me dejan el video como quiero. Al final lo consigo, como no, usando directamente la línea de comandos con ffmpeg. Y ahora empezamos con los "return":
    • Convierto el video a avi
    • Edito el video y le corto lo que sobra
    • Subo el video a youtube
    • termino el tutorial en la chuwiki
    • escribo otro tutorial sobre las herramientas de video probadas.
    • y ya son las y pico de la madrugada y me voy a la cama.

Al final, pasé toda la tarde (y parte de la noche) entretenido, no hice nada de la aplicación que quería hacer, pero he subido mi primer video a youtube (no tiene demasiada buena calidad), he escrito par de tutoriales en la wiki y he aprendido algunas cosas sobre Tomcat: DataSources y permisos.

¿De verdad los ordenadores ahorran trabajo?