[ Blog ]
[ abuabalras ]
[ Wiki ]
[ Slideshare ]
[ Twitter ]

 

Bloggers recientes

Daniel Sánchez Mensajes: 1
Estrellas: 0
Fecha: 25/01/12
Gustavo Fernandez Mensajes: 33
Estrellas: 7
Fecha: 16/01/12
Irune Prado Mensajes: 3
Estrellas: 0
Fecha: 13/01/12

noBlogo - El blog de zylk.net

Portlet de sincronizacion flexible y programada de usuarios de LDAP en comunidades de Liferay Portal EE

Durante los últimos dos meses un equipo mixto de zylk y Bilbomatica ha estado desarrollando, conjuntamente con personal de la UPV/EHU (Gabriel Maqueda y Alberto Soto), un portlet para la sincronización de usuarios en las comunidades de Liferay(version 6 EE). El escenario es el de una organización o universidad con miles de usuarios potenciales, administrados y centralizados en un sistema de directorio activo o LDAP corporativo, que necesita asignar  de una manera flexible, ágil y programada un conjunto de usuarios en las diferentes comunidades de un portal Liferay.

Los requisitos que se pretendía cubrir eran los siguientes:

  1. Poder delegar en los administradores de las comunidades la sincronización de los usuarios que pertenezcan a las mismas, siendo el origen de la sincronización el LDAP corporativo de la UPV/EHU.
  2. Permitir que los administradores de las comunidades puedan, sin tener conocimientos de la estructura del LDAP ni de la sintaxis de las querys, definir filtros para realizar la sincronización de dichos usuarios
  3. Permitir la planificación de las sincronizaciones con periodicidad variable y seleccionable por el propio administrador de la comunidad.
  4. Enviar informes cada vez que se ejecute una sincronización a una lista de usuarios definidos por el propio administrador.
  5. Traducción al euskera, tanto del interfaz como del asistente para las querys del LDAP, así como del informe resultante tras la sincronización de los usuarios de comunidades.

Nos planteamos primero resolver la duda técnica que teníamos antes de empezar el proyecto

  • Usar o no el planificador de tareas que Liferay integra, basado en quartz.

La conclusión fue que se podría hacer uso del planificador de tareas de Liferay con su modelo, lo cual permitía no tener que instanciar dos planificadores y que el propio portal sea capaz de gestionar parte de las tareas de manera transparente. Para ello el porltet se desplegó implementando el scheduler de Liferay con una periodicidad diaria y en la implementación del scheduler es donde se realiza la lógica de qué tareas de sincronización se deben ejecutar cada día.

Una vez decidida la arquitectura de la solución procedimos a su implementación. Desde un punto de vista funcional las características del desarrollo son las siguientes:

  • Altas en comunidad
  • Bajas en comunidad.
  • Importar del LDAP


El portlet permite personalizar los informes de la siguiente forma:

  • Seleccionar el idioma en que se generarán los informes. Los informes se podrán obtener tanto en castellano como en euskera.
  • Seleccionar el momento preciso en el que se va a realizar el informe o informes dependiendo del modo en que este se configure.
  • Obtener informes personalizados atendiendo a características y a valores concretos.
  • Gestionar y añadir destinatarios. Estos destinatarios recibirán los informes a través de su correo electrónico.
  • Enviar el mismo informe a múltiples destinatarios.
  • Seleccionar los datos que mostrará el informe.

En cuanto a la gestión de la expresión de LDAP de la cual se obtienen los usuarios que se van a sincronizar, el portlet dispone de un editor, desde donde el administrador de la comunidad puede seleccionar una característica y un valor. Además el usuario dispone de operadores lógicos ( AND, OR & NOT ), para poder realizar expresiones más complejas.
Cada comunidad dispone de una tarea que puede configurar para la sincronización como se muestra en la siguiente imagen:

 

El portlet está accesible desde el panel de control de cada comunidad, para los usuarios con rol de administrador de la comunidad.

La configuración se realiza desde la siguiente pantalla:

Además se puede definir la query de LDAP que nos devolverá los usuarios que se vayan a sincronizar:

También se pueden gestionar los destinatarios de las notificaciones:

Como temas destacables habría que resaltar:

 

  • La gestión de los atributos del LDAP es fácilmente extendible para que se pueda utilizar en un conjunto amplio de estructuras de LDAP. En el caso de la UPV/EHU el LDAP está basado en atributos, pero se podría usar en una estructura jerárquica. En zylk lo hemos probado con nuestro LDAP con resultados satisfactorios.
  • En una evolución de este desarrollo, se podría ampliar la parte de edición de receptores del informe, de manera que se pudieran añadir nuevos receptores a partir de los usuarios existentes en el portal. Así pues, la selección del idioma del informe se realizaría de manera transparente para el usuario del portlet y a partir de la configuración personal de idioma seleccionada por el usuario.
  • Se intentó crear un hook para que el portlet se integrara como una nueva acción en el portlet de comunidades pero no funcionó correctamente, así que se descartó esa posibilidad. Nos faltó mirar los listas blancas de portlets que se pueden añadir en tiempo de ejecución para ver si esa opción era viable.
ubuntu 10.04 LTS y algunos problemas con los applets

Lunes por la mañana y no se que ha pasado pero en el firefox de mi ubuntu, y en el de todos los de la empresa (usamos la 10.04 LTS), los applets han dejado de funciona. No he encontrado mucha información al respecto por internet pero he lincado a mano la librería y parece que todo vuelve a funcionar correctamente. Lo dejo aqui escrito por si puede servir a alguien más.

ln -s /usr/lib/jvm/java-6-sun/jre/lib/i386/libnpjp2.so   $HOME/.mozilla/plugins/libnpjp2.so

Algo tiene que ver con el plugin de java (sun-java6-plugin) porque usando el icedTea los applets funciona, el problema es que el icedTea usa una implementación de java que no es la de sun y en algunos temas de firma no funciona todo lo bien que me gustaría...

Seguiré investigando un poco más sobre el tema durante la semana, pero justo hoy necesitaba que los applets funcionaran para poder seguir con un proyecto, así que temporalmente me voy a quedar con la solución del link manual...

Desarrollando aplicaciones J2EE sobre Alfresco III - campos tipo datetime

 

Además de las particularidades sorteadas durante el desarrollo bajo Alfresco Community 3.4d, un curioso problema con el que nos encontramos fue el uso del tipo CMIS 'd:datetime'.

Por defecto, el analizador de tipo 'datetime' para Lucene de Alfresco tiene una implementación en donde sólo se contempla el 'date', dejando a un lado el 'time'. Como consecuencia de esto, las búsquedas 'ORDER BY cmis:creationDate' no conseguían resultados reales.

En la wiki nos recomiendan configurar un analizador tipo Datetime

 #d_dictionary.datatype.d_datetime.analyzer=org.alfresco.repo.search.impl.lucene.analysis.DateAnalyser
d_dictionary.datatype.d_datetime.analyzer=org.alfresco.repo.search.impl.lucene.analysis.DateTimeAnalyser

 

Tras reconstruir los índices todo funciona como debería. Lamentablemente, tener este tipo de analizador nos modificó el comportamiento de las búsquedas por fecha, no permitiendo encontrar objetos mediante el uso de predicados CMIS-SQL tipo:

 SELECT * FROM eu:registro WHERE cmis:creationDate > '2011-01-10T09:09:55.965Z'

SELECT * FROM eu:registro WHERE cmis:creationDate > '2011-01-10T09:09:55.965Z' 
AND cmis:creationDate < '2011-01-12T09:09:55.965Z'

SELECT * FROM eu:registro WHERE cmis:creationDate>TIMESTAMP'2010-09-23T16:56:49.925+02:00' 

 

Tras probar varias sugerencias leídas en la comunidad Alfresco [1] [2] [3] y no conseguir solución decidimos cambiar de estrategia.

Decidimos abandonar el uso del tipo 'd:datetime' a favor de 'd:long' para almacenar las fecha como TimeMiliseconds desde la fecha base de un calendario J2EE , denominado época (1 January 1970 00:00:00 GMT)

 <property name="eu:FechaInicioTimeMiliSeconds">
    <title>Fecha de inicio miliseconds</title>
    <type>d:long</type>
</property>

 

 metadatos.put(
  METADATA_FECHA_REGISTRO_MILISECONDS,
  Formatters.convertTimeToMiliseconds(io.getFechaRegistro())
);


Y ya lo tendríamos todo casi solucionado, si no fuera porque en la implementación CMIS de Alfresco el tipo d:long de CMIS no se interpretaba como tal, si no como un entero tipo d:integer que al intentar recibir un 'timemiliseconds' nos generaba la excepción.

 Caused by: java.lang.NumberFormatException: For input string: "1320361200000"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
    at java.lang.Integer.parseInt(Integer.java:461)

 

Con un cambio de 'd:long' a 'd:string' todo solucionado!

Usando hadoop para intercambio masivo de ficheros en un contexto de big data

Durante los últimos tres meses en zylk hemos estado desarrollando, conjuntamente con personal de EJIE (Oscar Guadilla en la definición de la arquitectura y gestión del proyecto, Carlos Gonzalez de Zarate y Roberto Tajada en la parte de platea integración y Juan Uralde en la parte de xlnets) , una aplicación horizontal para el intercambio temporal de ficheros. La problemática que se quería resolver era la siguiente:

  1. Disponer de un sistema para que las distintas aplicaciones, situadas en los distintos entornos (extra, intra, inter etc..), pudieran intercambiar ficheros.
  2. Disponer de un sistema de trazas/auditoria, consultable en tiempo real capaz de almacenar la información de millones de transacciones.
  3. Disponer de un sistema de eventos que permitiera la comunicación asíncrona entre las distintas aplicaciones (en lo que a intercambio de ficheros hace referencia)
  4. Disponer de una librería de cliente, para navegadores, que mejorara la experiencia de usuario a la hora de gestionar ficheros en aplicaciones web.
  5. Disponer de un API java que sirviera tanto para jdk 1.4, como para jdk 1.5+
  6. Disponer de un API batch.
  7. Disponer de un API WebService

El proyecto se dividió en dos partes.

  • Una primera en la que se hicieron las pruebas pertinentes para seleccionar las tecnologías y los patrones necesarios para el desarrollo. (Tres semanas de trabajo)
  • Una segunda en la que se realizó la implementación de la solución y se depuraron todos los aspectos que no se habían tenido en cuenta en la fase de análisis/pruebas (Nueve semanas de trabajo)

A continuación se muestra un gráfico de los distintos componentes que conforman las solución.

Y los distintos métodos que se pensaba crear y exponer en cada canal. Como eje principal de la solución se optó por usar hdfs (hadoop distributed file system1).
La idea original era crear una API java recubriendo el API original de hadoop para exponer los siguientes métodos:

  • move
  • info
  • copy
  • list
  • put
  • get

La primera pega que nos encontramos fue que el API de HADOOP solo se puede ejecutar con java 1.5+ lo que nos obligó a crear un nuevo canal de comunicación para exponer el API java 1.4. Para ello expusimos los métodos por medio de un API REST y creamos un cliente compatible con java 1.4 para dichos métodos.  Tanto el API para java 1.4 como el de 1.5 se instancian usando una factoría abstracta que permite cambiar del API 1.4 al API 1.5 con tan solo cambiar las implementaciones ya que creemos que en un futuro próximo todas las aplicaciones correrán con java 1.5+
A continuación mostramos un diagrama de como se han creado las implementaciones y las interfaces relacionadas con el proyecto.

 

Una vez tuvimos el core creado y consensuado nos centramos en el resto de las partes, a saber

  • Sistema de trazas.
  • Eventos (platea integración).
  • API WS.
  • Widget para upload.


A continuación presentamos un gráfico que resume todos los canales de comunicación entre los distintos APIs y componentes.

 

Donde se pude ver que al core se le han añadido las funcionalidades de trazas, metadatos y seguridad basada en xlnets.

Para el sistema de trazas se han utilizado colas JMS con un MDB que publica los mensajes en una base de datos noSQL como mongo-db. Se optó por esta solución porque después de hacer pruebas de rendimiento se conseguían un rendimiento entre 5-10 veces superior que en tablas relacionales. Hay que decir de todas formas que en este caso el concepto de noSQL encajaba a la perfección con el problema que se quería solventar al igual que los conceptos de listas finitas, orden natural inverso etc..

Para la parte de metadatos se ha optado por almacenar los mismos serializados en json en el propio hadoop, por un tema de auto-consistencia del filesystem. Aunque creemos que a esta parte habría que darle todavía una vuelta más e ir a un modelo de tablón basado en HBASE, por ejemplo. Si se hiciera esto se estudiaría también la posibilidad de cambiar el sistema de trazas a HBASE también.

Para el componente de upload se ha usado SWFUPLOAD, ya que se intentó usar los blobs de javascript que permiten no usar flash, pero internet-explorer en su versión 8 no los implementa todavía. Hay que destacar que para poder usar SWFUPLOAD en un entorno seguro que use las cookies para mantener la sesión hay que sortear unos pequeños problemas de flash con las cookies … pero eso es otra historia.

Para la parte de seguridad, simplemente se han usado las librerías de xlnets.

Para la parte de eventos se uso platea integración.

Otra de las partes que se usó, fue el map-and-reduce para el expurgo de ficheros, para lo que hadoop evidentemente nos sirvió.


Creo que esto es más o menos todo, la verdad es que la solución final es bastante potente y creemos que puede evolucionar satisfactoriamente y cubrir las necesidades para las que se ha diseñado.

Desarrollando aplicaciones J2EE sobre Alfresco II - pequeños consejos

Siguiendo el post de básicos para el desarrollo de aplicaciones J2EE sobre Alfresco, os dejamos una lista de pequeños consejos para el desarrollo con Alfresco, y en concreto para búsquedas con OpenCMIS.

Búsquedas con OpenCMIS

Para realizar las búsquedas, podemos seguir el consejo que os dimos en el post de básicos de Alfresco y usar la utilidad QueryStatement que nos ofrece la librería de OpenCMIS, o podéis construirlas a mano.

  • Comparadores CMIS-SQL

    • Si se requiere el uso de comparaciones case-sensitive, hacer uso del predicado CONTAINS() de CMIS-SQL para establecer cláusulas Full Text Search, en vez de el comparador básico provisto por CMIS-SQL =' '. Ejemplo:

       SELECT * FROM eu:registro as r join cm:titled as t on r.cmis:objectId=t.cmis:objectId 
      join cmis:folder as f on r.cmis:objectId = f.cmis:objectId 
      WHERE CONTAINS(t,'cm:title:\'*convenio*\'') ORDER BY r.cmis:lastModificationDate DESC 
    • Para las búsquedas de campos marcados como constraints, el predicado '=' de CMIS-SQL funciona como si utilizáramos el predicado LIKE. Es por eso que por ejemplo; buscando

       SELECT eu:EstadoArchivo FROM eu:registro  WHERE eu:EstadoArchivo='Expedientar'

      Bajo el modelo

       <property name="eu:EstadoArchivo">
      	<title>Estado Archivo</title>
      	<type>d:text</type>
      	<constraints>
      		<constraint ref="eu:listaEstadosArchivo" />
      	</constraints>
      </property>
      
      <constraint name="eu:listaEstadosArchivo" type="LIST">
      	<parameter name="allowedValues">
      		<list>
      			<value>Pendiente revisar</value>
      			<value>Expedientar</value>
      			<value>No expedientar</value>
      		</list>
      	</parameter>
      </constraint

Se encuentran tanto los valores de 'Expedientar' como 'No expedientar'.

En nuestro caso arreglamos el problema cambiando el valor de 'Expedientar' por 'Si expedientar'

 SELECT eu:EstadoArchivo FROM eu:registro WHERE eu:EstadoArchivo='Sí Expedientar'
  • Uso del predicado IN_TREE

    Para agilizar las búsquedas, el uso del predicado IN_TREE resulta de gran ayuda, ya que nos asegura buscar objetos dentro del árbol de un nodo determinado.  Ejemplo:

     SELECT * FROM eu:registro as r 
    join cm:titled as t on r.cmis:objectId=t.cmis:objectId 
    join cmis:folder as f on r.cmis:objectId = f.cmis:objectId 
    WHERE IN_TREE(f, 'workspace://SpacesStore/8d18d03a-8fe6-4ede-bfcd-5f6435d78b9e') ORDER 
    BY r.cmis:lastModificationDate DESC
  •  

  •  

  •  

  • Métodos de paginación

    La interfaz ItemIterable que nos ofrece OpenCMIS nos da todo lo necesario para realizar una paginación en servidor, gracias al los métodos skipTo(posición) y getPage().

    Con esto la búsqueda paginada nos quedaría algo tal que

 public ItemIterable<QueryResult> doQuery(String query, int delta, int page) {		

log.trace("doQuery | delta: " + delta + " | page: " + page);
Session session = this.getSession(this.username);

OperationContext contexto = session.createOperationContext();

if (delta != -1){
  contexto.setMaxItemsPerPage(delta);
}
		
log.debug("doQuery | " + query);
ItemIterable<QueryResult> results = session.query(query, false, contexto);
log.trace("doQuery | nº resultados: " + results.getTotalNumItems());

if (page > 1){
  log.trace("skip to " + (page*delta-delta));
  results = results.skipTo(page*delta-delta); }
  return results.getPage(); }

 

 

 

 

 

 

 

Otros consejos

  • Uso de caracteres especiales

    A la hora de crear ficheros / directorios en Alfresco, es conveniente recordar que existen ciertos caracteres que no son válidos. Sin embargo, es útil saber que no tenemos porqué deshacernos del nombre original de lo que queremos guardar (a pesar de que incluya alguno de estos caracteres), ya que podemos hacer uso del metadato 'Title', para insertar estos caracteres, y luego recuperarlos desde nuestra aplicación.

Mostrando el intervalo 1 - 5 de 141 resultados.
Resultados por página 5
de 29