Jun 21

Liferay: Obtener el nombre de usuario desde JSP

 Para obtener el nombre del usuario o cualquier otro dato del mismo desde un portlet de Liferay y en concreto desde el jsp (view.jsp o cualquier otro), podemos hacer lo siguiente.

En view.jsp, ponemos 

<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme" %>

<liferay-theme:defineObjects />

 

El defineObjects nos define un montón de variables java accesibles para nuestro código jsp, entre ellas una variable "user" de esta API. Podemos llamar a cualquiera de sus métodos para obtener cualquier dato que nos interese, por ejemplo, la dirección de correo

<%= user.getEmailAddress() %>

 

Aquí tienes todos los objetos definidos con liferay-theme:defineObjects.

The objects that are injected into the pageContext by the <liferay-theme:defineObjects /> tag are:

  • themeDisplay – com.liferay.portal.theme.ThemeDisplay
  • company – com.liferay.portal.model.Company
  • account – com.liferay.portal.model.Account (deprecated)
  • user – com.liferay.portal.model.User
  • realUser – com.liferay.portal.model.User
  • contact – com.liferay.portal.model.Contact
  • ?layout – com.liferay.portal.model.Layout
  • ?layouts – List<com.liferay.portal.model.Layout>
  • plid – java.lang.Long
  • ?layoutTypePortlet – com.liferay.portal.model.LayoutTypePortlet
  • portletGroupId – java.lang.Long
  • permissionChecker – com.liferay.portal.security.permission.PermissionChecker
  • locale – java.util.Locale
  • timeZone – java.util.TimeZone
  • theme – com.liferay.portal.model.Theme
  • colorScheme – com.liferay.portal.model.ColorScheme
  • portletDisplay – com.liferay.portal.theme.PortletDisplay

 

Extraído de http://www.liferay.com/web/raymond.auge/blog/-/blogs/809893

 

Mar 15

Jugando a importar usuarios LDAP en Liferay

Logo LiferayLiferay se puede configurar para que busque los usuarios en un directorio LDAP. La configuración se hace desde la ventana de administración de Liferay y es más o menos sencilla si se conoce cómo funciona LDAP.

Sin embargo y como ya estamos acostumbrados, no todo es tan fácil y siempre hay pegas, peros y contras.

Entre los datos de los usuarios de Liferay (nombre, correo, teléfono, etc) hay un campo denominado Screen Name que liferay usará en la URL de las páginas personales del usuario. Si el Screen Name es "federico", la URL de las páginas personales de Federico será algo como http://un.servidor.liferay/…./federico/….  Por ello, liferay verifica que lo que se mete en este campo es amigable para una URL y es muy estricto con los caracteres que admite. No admite acentos, ni eñes …. ni espacios en blanco ni incluso subrayados _  . Tampoco permite que dos usuarios distintos tengan el mismo Screen Name.

Cuando importamos usuarios de LDAP sobre Liferay tenemos que indicar qué campo de LDAP será el Screen Name y lo normal es que Liferay use el CN del usuario o el UID, que son los que identifican un usuario y tenemos garantía en LDAP de que son distintos. Y aquí empiezan los problemas. LDAP admite de todo en sus  campos, espacios, acentos, eñes y gurruños … pero Liferay no. La mejor forma de tener éxito creando usuarios en LDAP para Liferay es asegurarse que el campo CN o UID (el que elijamos para Screen Name de Liferay) cumple con lo exigido por Liferay.

Y si no cumple, podemos hacer ciertas "trampas".

En primer lugar, entre las ventanas de administración de Liferay para los temas de usuarios hay un check para "auto generar nombres de usuario" (control panel -> Portal Settings -> Users en la versión 6.0.6 de Liferay). Podemos marcarlo y luego importar los usuarios de LDAP. Esto hará que Liferay genere los Screen Names de forma correcta. Pero nos abre un segundo problema. Cuando Liferay intente buscar al usuario en LDAP, no lo va a encontrar, porque usará como CN o UID el nombre auto generado y no el real de LDAP. Así que debemos dar un segundo paso, un poco chapucero y que no debe hacerse nunca … salvo que no quede otro remedio.

Si son muchos usuarios, nos vamos a la base de datos de Liferay y en la tabla user_ están los screen names auto generados por Liferay. Con un pequeño programita o script que nos hagamos, podemos ir leyendo los CN o UID de LDAP e ir haciendo los update correspondientes en la base de datos de Liferay para poner como Screen Name el valor CN o UID de LDAP.

Si son pocos usuarios, podemos editar los Screen Names desde el mismo Liferay … .pero tenemos otro pequeño truco que hacer antes. Hay que decirle a Liferay que sea "liberal" a la hora de admitir Screen Names. Hay una propiedad en portal.properties (o portal-ext.properties) que se puede cambiar 

# Este es el valor que tiene por defecto en portal.properties
users.screen.name.validator=com.liferay.portal.security.auth.DefaultScreenNameValidator

# Y este es el que debemos poner en portal-ext.properties
users.screen.name.validator=com.liferay.portal.security.auth.LiberalScreenNameValidator
 

Con esto ya podemos poner el Screen Name que nos dé la gana a cada usuario desde el editor de Liferay, así que podemos ir cambiándolos uno a uno para poner los CN o UID reales de LDAP. Esta propiedad no afecta a la importación de usuarios de LDAP, por lo que no sirve de nada fijarla antes de importar los usuarios.

Una vez hecha la importación y cambiados los Screen Names, bien en base de datos directamente, bien desde el editor de Liferay, tenemos que acordarnos de desmarcar la casilla de autogenerar Screen Names.

Listo, usuarios importados. Y a rezar cuando alguien quiera ver sus páginas personales y le salga una URL de lo más raro.

 

Mar 10

Liferay: Migrando de HSQLDB a MySQL

 Liferay viene con una base de datos HSQLDB por defecto. La intención es que con cero instalación tengamos nuestro portal funcionando con sus datos de ejemplo. Sin embargo, el log de liferay al arrancar nos avisa que estamos usando la base de datos HSQLDB, no adecuada para un entorno de producción y nos ruega encarecidamente configurar Liferay con una base de datos en serio, como MySQL, Oracle, etc.

Por supuesto, con las prisas de todo proyecto, ni puñetero caso. Y tras unos meses de desarrollo y con algo más de tiempo, llega el momento de hacer la migración. Este es el mejor tutorial que he encontrado para hacerlo y en el que me he basado. Pero como todo informático experimentado sabe, las cosas nunca van bien a la primera. Aquí mis pasos, problemas y soluciones, no tan detallados como en el tutorial.

Primero de todo, por supuesto, paramos nuestro portal liferay. Del directorio …/liferay-6.0.6/data/hsql cogemos los dos ficheros lportal.properties y lportal.script que son los que tienen nuestros datos en HSQLDB. Hacemos copia de seguridad, por supuesto, los borramos de ahí (junto al directorio hsql) porque ya no los usaremos.

El fichero hsql.script hay que cambiarlo con un editor de textos con lo que indica el enlace anterior

  • Cambiar un par de líneas en las que aparece PUBLIC por LPORTAL. Esta última palabra será el nombre de la base de datos en MySQL donde vamos a hacer la migración. Las líneas donde aparece PUBLIC son las de CREATE SCHEMA y SET SCHEMA.
  • Cambiar todas las fechas 1970-01-01 por 1970-01-02, porque MySQL no admite la primera por lo visto.

Con la herramienta MySQL migration Toolkit hacemos la migración de HSQLDB a MySQL. Primero, antes de arrancar la herramienta, debemos poner el hsqldb.jar en el directorio C:\Program Files\MySQL\MySQL Tools for 5.0\java\lib para que la herramienta tenga disponible el driver de la base de datos HSQLDB.

Una vez arrancada la herramienta, los parámetros de conexión a la base de datos son

Source – HSQLDB
Driver Class: org.hsqldb.jdbcDriver
Connection String: jdbc:hsqldb:file:LIFERAY_INSTALLATION\data\hsql\lportal (Sin extension)
Username: sa
Password: <empty>

Target – MySQL
Host: localhost
Port: 3306
Username: root
Password: <empty>

Por supuesto, el path de la conexión HSQLDB será donde tengamos copiado nuestro fichero lportal.script. Es necesario que el lportal.properties esté en el mismo directorio.

Seguimos con los pasos de la herramienta y nos da hasta cuatro opciones de las que podemos elegir las que queramos

  • Crear las tablas directamente en la base de datos MySQL
  • Crear un script con la creación de tablas para MySQL
  • Insertar los datos en la base de datos MySQL
  • Crear un script con los insert para MySQL

Bueno, el tutorial dice que basta con hacerlo directamente sobre MySQL y así debería ser, pero a mí me han surgido problemas.

La herramienta protesta porque algunas tablas de HSQLDB tiene columnas de tipo texto con longitudes muy, muy grandes y aconseja campos tipo BLOB. Esto hace que la herramienta directamente no cree esas tablas.

Si generamos el script de creación de tablas y lo ejecutamos, ya no tenemos esos problemas con esos campos, pero protesta porque en una tabla concreta se usa un index con uno de esas columnas de texto gigantes y MySQL no lo permite.

En cuanto a los insert, la opción directa no funciona porque faltan tablas. Así que generamos el script y nos quedamos con él, es lo único de los cuatro pasos que nos sirve.

¿Y cómo he creado las tablas entonces?. Pues he dejado que sea Liferay el que lo haga por mí. Borré el directorio liferay-6.0.6/data/hsql, con lo que si arrancamos liferay empezaría desde cero. Pero antes de arrancarlo lo configuro para que use MySQL

  • Pongo el jar con el driver de MySQL en un sitio que lo encuentre Liferay, en concreto lo puse en ../liferay-6.0.6/tomcat-xxxx/ROOT/WEB-INF/Lib/mysql-connector.jar.
  • Me aseguro que el fichero ../liferay-6.0.6/tomcat-xxxx/bin/setenv.bat o setenv.sh (el que usemos) tiene en sus JAVA_OPTS un -Dexternal-properties=portal-ext.properties. Esto hará que se lea el fichero portal-ext.properties
  • Ese fichero está en …/liferay-6.0.6/tomcat-xxxx/webapps/ROOT/WEB-INF/classes/portal-ext.properties. Lo editamos y ponemos la configuración para MySQL

jdbc.default.driverClassName=com.mysql.jdbc.Driver
jdbc.default.url=jdbc:mysql://localhost/lportal?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false
jdbc.default.username=un usuario
jdbc.default.password=una password

Por supuesto, en la url pondremos lportal o el nombre que nos guste, pero debe ser el mismo que el que hayamos puesto cuando hicimos la migración de HSQLDB. Sólo queda arrancar liferay y esperar que cree todas las tablas. Matamos liferay, hacemos un backup de las tablas recién creadas sin datos, sólo la estructura y ya tenemos un script que crea las tablas. El comando para ello puede ser algo como

mysqldump -u usuario -p –no-data –databases lportal > crea_tablas_liferay.sql

Ahora ya podemos borrar en MySQL la base de datos completa, volver a crearla y ejecutar los scripts de creación de tablas e inserción de datos. Desde la consola de MySQL

drop database lportal;
create database lportal;
use lportal;
source /path/crea_tablas_liferay.sql
source /path/Inserts.sql

donde path es donde tengamos los scripts e Inserts.sql el el script de inserciones obtenido con MySQL migration Tool. Me tropecé también con un pequeño detalle de MySQL mayúsculas/minúsculas, así que tenlo en cuenta antes de crear las tablas e insertar los datos si estás en linux.

Y listo, arrancando ahora Liferay debemos tenerlo igual que antes, pero trabajando sobre MySQL en vez de sobre HSQLDB.

Otro enlace que me fue útil es este, aunque es para otra herramienta de Atlassian.

Feb 22

log4j y liferay

Andamos desarrollando portlets para liferay y claro, queremos poner nuestro log de debug, de info y demás en algún sitio. Como también desarrollamos algunos jar que se usarán en más sitios aparte del portlet y queremos que esos jar también tengan log, usamos log4j y no el logger que se aconseja usar con liferay

// Para portlets liferay
Log log = LogFactoryUtil.getLog(UnaClase.class);

// Con log4j
Logger log = Logger.getLogger(UnaClase.class);

 

siendo Log y LogFactoryUtil clases específicas de los jar de liferay y por tanto útiles para un portlet, pero no para una librería. Pos ese motivo, queremos usar log4j. Dicho y hecho, a ello.

Pero aparece un problema, en el log del tomcat donde está instalador nuestro liferay, en vez de aparecer el log de estas librerías usadas por nuestro portlet, aparece el siguiente error

og4j:ERROR A "org.apache.log4j.ConsoleAppender" object is not assignable to a "org.apache.log4j.Appender" variable.
log4j:ERROR The class "org.apache.log4j.Appender" was loaded by
log4j:ERROR [WebappClassLoader
delegate: false
repositories:
/WEB-INF/classes/
———-> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@1ac1fe4
] whereas object of type
log4j:ERROR "org.apache.log4j.ConsoleAppender" was loaded by [WebappClassLoader
delegate: false
repositories:
/WEB-INF/classes/
———-> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@1ac1fe4
].
log4j:ERROR Could not instantiate appender named "CONSOLE".

Vamos, que hay algún tipo de problema al intentar usar log4j en nuestra librería. Buscando por internet, se llega a la conclusión de que es un bug de liferay y que de alguna forma se lía con los classloader y los log4j.jar que pueden existir en nuestra aplicación portlet y los que ya tiene liferay. Buscando soluciones, la gente propone muchas y a algunos les funcionan y a otros no, otras parecen complejas y lo cierto es que casi todas las que he probado no me funcionan.

Y digo casi todas porque hay una que sí me ha funcionado y es poner la variable de entorno -Dlog4j.ignoreTCL=true. Esto debe hacer que log4j ignore esos errores y siga funcionando. He añadido esta variable al fichero /liferay_home/tomcat_xxxx/bin/setenv.sh y todo va de perlas.

El siguiente problema son los niveles de DEBUG. Por defecto deben salir de INFO para arriba. A mi me interesan los de DEBUG de nuestras clases. Tampoco he visto forma adecuada de configurarlo, pero lo he conseguido de una manera que no me gusta mucho. En webapps/ROOT/WEB-INF/classes del tomcat de liferay hay un log4j.properties. Ahí podemos configurar el nivel que queremos para nuestros logs y otras cosas propias de log4j. Basta añadir una línea con

log4j.logger.com.chuidiang.libreria=DEBUG

donde log4j.logger es fijo y com.chuidiang.libreria es el paquete por el que empiezan las clases de mi librería.

Por fin, el log4j funcionando de perlas.

 

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.