Jan 26

Más de JSTL: core_rt y obtener el locale fijado con fmt:setLocale

Bueno, sigo jugando con JSTL. Un par de cosillas que he descubierto estos días.

JSTL core_rt

En el post anterior mencionaba una posible forma de acceder a las constantes de un bean desde jstl. Otra forma es, en vez de usar el JSTL core, usar el JSTL core_rt, que está pensado para no usar las expresion EL típicas de JSTL core como ${variable}, sino para usar y tener acceso a las mismas variables y expresiones de los scriptlets que van entre <% … %>

Con JSTL core haríamos algo como

<jsp:useBean id="bean" class="com.chuidiang.UnaClase"></jsp:useBean>

<c:out value="${bean.UNA_CONSTANTE}" />

y que requería del método getUNA_CONSTANTE() en el bean UnaClase

Pues bien, otra opción para esto es usar core_rt

<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c_rt"%>

y esto nos permitiría hacer directamente

<c_rt:out value="<%= UnaClase.UNA_CONSTANTE %>"/>

donde hemos podido usar un normalito <%= …. %>

Con core_rt tenemos todos los tags de core, con la diferencia de que usaremos <%= …%> en vez ${ …. }

Obtener el locale fijado con fmt:setLocale

Otro de los grupos de tags de JSTL es para la internacionalización (i18n que dicen ahora, ya que entre la i y la n de "internacionalización" hay 18 letras). Importando la taglib adecuada

<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>

A partir de ahí tenemos fmt:setBundle para indicar dónde están nuestros ficheros de propiedades con los textos en los distintos idiomas, fmt:message que nos permite mostrar textos al usuario según el idioma seleccionado (por defecto el del navegador) y fmt:setLocale si queremos un idioma distinto del navegador (por ejemplo, para ofrecer al usuario que lo solicita otro idioma distinto). Hay más tag para formateo de fechas y números de acuerdo al idioma elegido.

Pero echo en falta de menos un fmt:getLocale. Si cambiamos el locale con fmt:setLocale, no hay forma simple de saber más adelante cual hemos puesto. Pero sí la hay, un poco rebuscada. Si hacemos este cambio

<fmt:setLocale value="en_UK"/>

el scope por defecto es page y podemos obtener el locale con

<c:out value="${pageScope['javax.servlet.jsp.jstl.fmt.locale.page']}"/>

y si lo cambiamos con scope="session" para que afecte a todas las páginas en la sesión

<fmt:setLocale value="en_UK" scope="session"/>

se puede obtener más adelante o en otra página con

c:out value="${sessionScope['javax.servlet.jsp.jstl.fmt.locale.session']}"/>

¿Y para qué es útil esto?. Pues no lo tengo claro, pero comento para qué me ha hecho falta. La aplicación también tiene javascript y también tiene textos en los javascript (por ejemplo, en las ventanas confirm() o alert() antes de borrar algo). Para internacionalizar javascript suele ser necesario incluir algún fichero .js con los textos. También es habitual que haya un fichero distinto para cada lenguaje, por ejemplo es.js, en.js, etc. Y claro, no vamos a incluir todos estos ficheros en nuestra página, sólo deberíamos incluir el necesario para la visualización concreta que estemos haciendo. Pues una forma de conseguirlo es hacer la inclusión de esta manera

<script src=’path/<c:out value="es.js"/>’  …. ></script>

por supuesto, sin poner es.js a piñón fijo, sino obteniéndolo con el pequeño chorizo anterior. Quedaría tan feo como esto

<script src=’path/<c:out value="${sessionScope['javax.servlet.jsp.jstl.fmt.locale.session']}.js"/>’ ….></script>

 Eso sí, tampoco es tan simple, ya que si no hemos hecho un fmt:setLocale, ese valor no existe y es null. Tenemos que recoger entonces el locale de ${pageContext.request.locale}, que es donde está el locale del navegador. Así que nos tocará hacer un if ….

Jan 17

Constantes en los beans y JSTL

A veces, en una aplicación web con JSTL nos vendría bien tener una constante definida en uno de los beans de java, estilo

public class UnaClase {
   public static final String UNA_CONSTANTE = "algo";
   …
}

y luego poder llamarla desde JSTL con algo como

<jsp:useBean id="bean" class="com.chuidiang.UnaClase"></jsp:useBean>

<c:out value="${bean.UNA_CONSTANTE}" />

Desgraciadamente, eso no funciona. JSTL presupone que UNA_CONSTANTE es un atributo del bean y va a convertir ese ${bean.UNA_CONSTANTE} en una llamada al método bean.getUNA_CONSTANTE() que no existe.

La solución es fácil, basta con crear un método getUNA_CONSTANTE() en el bean que devuelva el valor de la constante. Unicamente tenemos que tener en cuenta que ese método no puede ser static, puesto que si no JSTL tampoco lo llamará (por debajo, probablemente usa la introspección de java y no busca métodos estáticos). El bean quedaría así

public class UnaClase {
   public static final String UNA_CONSTANTE = "algo";

   public String getUNA_CONSTANTE () {
      return UnaClase.UNA_CONSTANTE;
   }

}

que sí funciona como esperamos.

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.

 

Jan 10

Un eBook

wibook 650tEstas navidades a los reyes les ha dado por traer eBooks. A mi madre le han traído uno, el Kindle de Amazon. A mi me ha tocado el wibook del Corte Inglés. Y he jugado un poco con ambos, pero sin matarme, tampoco soy de los que andan mirando al detalle estos chismes, sólo lo quiero para leer. Y así, en un poco de jugar, ¿qué diferencias veo entre uno y otro?.

La más evidente es la pantalla táctil del wibook. La del Kindle no lo es. No tiene especial importancia, ya que entre pasar página apretando un botón o arrastrando el dedo sobre la pantalla no hay demasiada diferencia. Sí se nota si alguien es de los que se dedica a escribir notas. Es más cómodo un teclado dibujado sobre una pantalla táctil que un teclado sobre una pantalla en la que no puedes tocar y la forma de pulsar teclas es usar las flechitas para seleccionar la tecla y luego pulsar el botón de en medio para hacer click en la tecla. También es más cómodo para teclear un número de página a la que quieres ir.

Una pega para el wibook que sí me molesta un poco más. El encendido del Kindle es inmediato y te muestra la última página que tuvieras abierta al apagarlo. El wibook tarda un poco en encenderse ¿unos 20 segundos? y siempre te muestra el menú de inicio, por lo que inmediatamente después de encenderlo debes pulsar en la pantalla para abrir el libro que estás leyendo (sólo una pulsación). Teniendo en cuenta que pasada la novedad del eBook el 99% de las veces lo encenderemos para leer, se agradece más el encendido rápido y directo en la página a leer, como hace el Kindle.

En cuanto al tema reflejos…. simplemente un detalle que yo no sabía. Casi todos los eBooks dicen tener pantalla anti-reflejos. Bueno, eso hay que entenderlo. Se refieren a que puedes leer con ellos en la calle con luz natural. En general los displays de los dispositivos como móviles o cámaras de fotos se ven bastante mal si les da la luz del sol más o menos directa. No lo he probado pero en los eBooks en principio esto no pasa y es lo que quiere decir anti-reflejos. Sin embargo, si lees en casa con una lámpara… hay que orientar el eBook de forma adecuada para que la lámpara no refleje en la pantalla, que sí refleja. No he comparado ambos en detalle, pero me da la impresión de que el kindle refleja algo menos.

En cualquier caso, estoy contento con el regalo (un eBook), puesto que soy lector habitual, (sí caen alrededor de una decena de novelas al año), me gusta leer en las cafeterías (y algunos libros pesan más de la cuenta) y tengo una habitación llena de estanterías llenas de libros en varias filas y montones.