Archivos mensuales: agosto 2011

Problema con Edit Online en CIFS con Alfresco (y solución)

En Alfresco se puede producir un problema si se activa CIFS y la edición en línea para crear y modificar documentos en MS-Office.

Problema:
Cuando se crea o modifica un documento en Word/Excel, Alfresco asigna el aspecto «ownable» con el valor de «owner» a null. Esto provoca que si dentro del espacio de trabajo, el usuario que lo ha creado o modificado tiene el rol «Editor», no se puede borrar (no aparece el icono de la papelera ni se puede invocar a la acción de borrar).

Investigando un poco vemos que efectivamente al documento creado desde Word en la carpeta compartida de CIFS que controla Alfresco se le asigna el aspecto con el valor «owner» a null.

Este efecto puede observarse mejor en el explorador de nodos.

Entorno:
– Alfresco ECM 3.3.5
– RedHat 5
– MySQL 5
– Autenticación: Kerberos+SSO (Active Directory)

Solución:
He creado dos soluciones con scripts en JavaScript. Una de ellas lo que hace es eliminar el aspecto directamente y la segunda lo que hace es asignar el valor del campo «creator» al campo «owner» del aspecto «ownable».

Ambas soluciones son válidas y solo hay que elegir la que mejor venga en cada ocasión. En mi caso, la primera es quizás más acorde con el funcionamiento normal del Alfresco, ya que al subir un nuevo documento, este no asigna nunca el aspecto «ownable».

Implementación:
Tanto una como otra solución consisten en scripts en JavaScript que tienen que ser llamados por ejemplo desde una regla asignada al espacio donde se crean los documentos directamente en Word vía unidad compartida con CIFS. Deben crearse dos reglas, una para los nuevos documentos y otra para cuando sean modificados.

Scripts:
Fichero: removeAspect_ownable.js

// Elimina aspecto owneable (problema en edit-online con office – 1a solución)
if (document.properties[«cm:owner»] == null)
{
    logger.log(«Eliminando owner vacío para el documento «+document.properties[«name»]);
    document.removeAspect(«cm:ownable»);
}

Fichero: creatorToOwner.js

// Asigna como owner el creator (problema en edit-online con office – 2a solución)
if (document.properties[«cm:owner»] == null)
{
    logger.log(«Asignando «+document.properties[«cm:creator»]+» al owner vacío para el documento «+document.properties[«cm:name»]);
    document.properties[«cm:owner»] = document.properties[«cm:creator»];
    document.save();
}

De esta forma, queda solucionado este problema hasta que sea solucionado por parte de Alfresco en siguientes versiones.

Más información:
JavaScript API para Alfresco: http://wiki.alfresco.com/wiki/JavaScript_API
Reglas y acciones en Alfresco: http://wiki.alfresco.com/wiki/Actions_and_Rules
Crear reglas y acciones (en Share): http://www.youtube.com/watch?v=1NL8a-6dU7Y

Mantenimiento diario de Alfresco

Una vez instalado Alfresco, sea el método que sea el que se ha usado para su instalación (bundle, instalador, instalación separada de servidor de aplicaciones más producto, etc.), es necesario realizar una serie de tareas de mantenimiento diarias para no encontrarnos posteriormente con desagradables sorpresas.

El problema más común en este sentido es que se llene alguna de las particiones o discos que se están usando por parte de Alfresco para almacenar ficheros temporales, documentos borrados o logs.

Para ello, se pueden crear una serie de tareas y scripts para tener un mantenimiento automatizado. En mi caso uso un mantenimiento que termina borrando definitivamente ficheros, en otras situaciones es preferible realizar copias antes de estos borrados.

Lo primero que hay que hacer es tener bien colocado el fichero de logs de Alfresco,  para esto, copiamos el fichero log4j.properties como custom-log4j.properties dentro del extension. Por ejemplo:

cp /opt/alfresco/tomcat/webapps/alfresco/WEB-INF/classes/log4j.properties /opt/alfresco/tomcat/shared/classes/alfresco/extension/custom-log4j.properties

Y seguidamente ponemos el directorio correcto donde se almacenarán los logs. En este caso el siguiente valor:

Fichero: custom-log4j.properties

[…]
log4j.appender.File.File=/opt/alfresco/tomcat/logs/alfresco.log
[…]

Ahora los ficheros de logs propios de Alfresco se crearán dentro del directorio apropiado.

Seguidamente, aunque Alfresco ya realiza una rotación de estos logs por fechas, se puede crear un pequeño script para comprimir los ficheros de logs que han sido rotados:

Fichero: cron_gzip_logs.sh

#!/bin/bash
. `dirname «$0″`/config
for i in `ls ${CATALINA_HOME}/logs/alfresco.log.*[!.gz] 2>/dev/null`
do
        gzip $i
done

Además, podemos borrar los ficheros de logs aunque ya hayan sido rotados y comprimidos pero que tengan un número de días y por tanto ya no nos interesen. Para ello he creado el siguiente script:

Fichero: cron_del_logs.sh

#!/bin/bash

. `dirname «$0″`/config
find ${CATALINA_HOME}/logs/alfresco.log.*.gz -mtime +${AFTER_DAYS} -delete 2>/dev/null

También necesitamos borrar ficheros temporales que hayan sido creados hace días y no se hayan eliminado:

Fichero: cron_del_temp.sh

#!/bin/bash
. `dirname «$0″`/config
find ${CATALINA_HOME}/temp -mtime +${AFTER_DAYS_TEMP} -delete 2>/dev/null

En Alfresco, los ficheros/documentos que ya no están en la papelera y han pasado más de 14 días son movidos a un directorio generalmente llamado contentstore.deleted. Este directorio no es borrado nunca por Alfresco por lo que si se realizan muchas modificaciones de documentos y borrados puede llenarse y ocupar mucho espacio. Para solucionar esto he creado el siguiente script:

Fichero: cron_del_deleted.sh
#!/bin/bash
. `dirname «$0″`/config
find ${CONTENTSTORE_DELETED}/* -mtime +${AFTER_DAYS_DELETED} -delete 2>/dev/null

Ahora solo queda crear el fichero de configuración donde se asignan los valores a las variables usadas:

Fichero: config

CATALINA_HOME=/opt/alfresco/tomcat
AFTER_DAYS=30
AFTER_DAYS_DELETED=5
AFTER_DAYS_TEMP=5
CONTENTSTORE_DELETED=/opt/alfresco/alf_data/contentstore.deleted

Y el fichero maestro que llamará a todos los scripts (al que he llamado adt.sh en alusión a Alfresco Daily Tasks) y que servirá para ponerlo en el cron diario:

Fichero: adt.sh
#!/bin/bash
#
# @Author: Fernando Gonzalez
# @Version: 1.0
# @Fecha: 2011
#
UTILS_DIR=/opt/alfresco/utils
${UTILS_DIR}/cron_gzip_logs.sh
${UTILS_DIR}/cron_del_logs.sh
${UTILS_DIR}/cron_del_temp.sh
${UTILS_DIR}/cron_del_deleted.sh

Y hacer el enlace simbólico dentro del directorio /etc/cron.daily o crearlo en la «crontab»:

cd /etc/cron.daily

ln -s /opt/alfresco/utils/adt.sh .

Esto es simplemente un ejemplo de las tareas que hay que hacer después de la instalación de Alfresco para el mantenimiento diario y así evitar problemas futuros.

Otras consideraciones: Dentro de este mantenimiento, recien instalado Alfresco y si estamos ante un sistema en cluster, recomiendo la lectura del siguiente artículo escrito por Toni de la Fuente sobre el sistema de «jobs» de Alfresco: http://blyx.com/2011/07/01/alfresco-scheduled-jobs-tareas-automaticas-de-mantenimiento/

Actualización (23/08/2011).

En el uso de OpenOffice.org, los ficheros temporales se crean por lo general en el directorio temporal del sistema operativo, en la mayor parte de los casos cuando se trata de Linux es en /tmp. El script para borrar los ficheros temporales del usuario que ejecuta Alfresco sería:

Fichero: cron_del_tmp.sh
#!/bin/bash
. `dirname «$0″`/config
find ${TMP}/ -mtime +${AFTER_DAYS_TMP} -uid $UID -delete 2>/dev/null

Hay que añadir al fichero config las dos líneas de variables usadas:
Fichero: config
[…]
AFTER_DAYS_TMP=5
TMP=/tmp

Y también la ejecución del nuevo script al fichero adt.sh:
[…]
${UTILS_DIR}/cron_del_tmp.sh

Es MUY IMPORTANTE que Alfresco siempre sea ejecutado por un usuario no root ya que de lo contrario si ocurre algún problema en el borrado de ficheros podría desestabilizarse e incluso romperse el sistema.

Procesos de OpenOffice.org y JodConverter en Alfresco

Alfresco aconseja el uso de JODConverter en contraposición de OpenOffice.org directamente para la transformación de documentos.

En la configuración típica de estos programas en el fichero alfresco-global.properties se establecen unos parámetros parecidos a los siguientes:

ooo.exe=soffice
ooo.enabled=false
jodconverter.officeHome=/usr/lib/openoffice.org3
jodconverter.portNumbers=8101
jodconverter.enabled=true

En este caso, se desactiva expresamente el uso de OpenOffice.org y se activa el uso de JODConverter.

Si comprobamos el proceso arrancado podremos ver que hay un OpenOffice.org en modo «escucha» para el puerto seleccionado, el 8101:

[root@alfpru1 bin]# ps -fea | grep openoffice
root      5057  4746  0 11:55 pts/0    00:00:00 /usr/lib/openoffice.org3/program/soffice.bin -accept=socket,host=127.0.0.1,port=8101;urp; -env:UserInstallation=file:///tmp/.jodconverter_socket_host-127.0.0.1_port-8101 -headless -nocrashreport -nodefault -nofirststartwizard -nolockcheck -nologo -norestore


[root@alfpru1 bin]# pstree -a
  ├─java -Declipse.security -Dwas.status.socket=52934 -Dosgi.install.area=/opt/IBM/WebSphere/AppServer-Dosgi.configuration.area=/opt/IBM/WebSphe
  │   ├─soffice.bin -accept=socket,host=127.0.0.1,port=8101;urp; -env:UserInstallation=file:///tmp/.jodconverter_socket_host-127.0.0.1_port-8101-headless
  │   │   ├─{soffice.bin}
  │   │   ├─{soffice.bin}
  │   │   ├─{soffice.bin}
  │   │   ├─{soffice.bin}
  │   │   ├─{soffice.bin}
  │   │   └─{soffice.bin}

Pero, en muchas ocasiones se dejan activados ambos sistemas o simplemente se comentan las líneas que comienzan con ooo. ¿Que pasa entonces?, ¿es excluyente el uso de JODConverter con respecto al uso directo de OpenOffice.org?
Pues bien, una vez comentadas las dos líneas siguientes:
#ooo.exe=soffice
#ooo.enabled=false

El resultado ha sido el siguiente:

[root@alfpru1 bin]# ps -fea | grep openoffice
root     24843 24510  0 12:55 pts/0    00:00:00 /usr/lib/openoffice.org3/program/soffice.bin -accept=socket,host=127.0.0.1,port=8101;urp; -env:UserInstallation=file:///tmp/.jodconverter_socket_host-127.0.0.1_port-8101 -headless -nocrashreport -nodefault -nofirststartwizard -nolockcheck -nologo -norestore
root     24851 24818  0 12:55 pts/0    00:00:00 /usr/lib/openoffice.org3/program/soffice.bin -accept=socket,host=127.0.0.1,port=8100;urp;StarOffice.ServiceManager -env:UserInstallation=file:///opt/alf343WAS7_data_cluster/oouser -nologo -headless -nofirststartwizard -nocrashrep -norestore


[root@alfpru1 bin]# pstree -a

  ├─java -Declipse.security -Dwas.status.socket=55052 -Dosgi.install.area=/opt/IBM/WebSphere/AppServer-Dosgi.configuration.area=/opt/IBM/WebSphe
  │   ├─soffice /usr/bin/soffice -accept=socket,host=127.0.0.1,port=8100;urp;StarOffice.ServiceManager -env:UserInstallation=file:///opt/alf343WAS7_data_cluster/oouser …
  │   │   └─soffice.bin -accept=socket,host=127.0.0.1,port=8100;urp;StarOffice.ServiceManager-env:UserInstallation=file:///opt/alf343WAS7_data_cluster/oouser
  │   │       ├─{soffice.bin}
  │   │       ├─{soffice.bin}
  │   │       ├─{soffice.bin}
  │   │       ├─{soffice.bin}
  │   │       ├─{soffice.bin}
  │   │       └─{soffice.bin}
  │   ├─soffice.bin -accept=socket,host=127.0.0.1,port=8101;urp; -env:UserInstallation=file:///tmp/.jodconverter_socket_host-127.0.0.1_port-8101-headless
  │   │   ├─{soffice.bin}
  │   │   ├─{soffice.bin}
  │   │   ├─{soffice.bin}
  │   │   ├─{soffice.bin}
  │   │   ├─{soffice.bin}
  │   │   └─{soffice.bin}

Como se observa, efectivamente NO SON EXCLUYENTES, con lo que hay que forzar la activación del método que se desea, desactivando el que no corresponda de forma explícita.

Alfresco 3.4.x ECM, WAS 7 y JGroups

Cuando se configuran tanto Alfresco ECM como IBM Websphere Application Server 7 (WAS7) en clúster, pueden presentarse algunos problemas o dificultades propias de este particular servidor de aplicaciones, entre estos está la configuración de JGroups.

La configuración es la siguiente:

2 nodos llamados alfpru1 (IP: 192.168.56.101/24) y alfpru2 (IP: 192.168.56.102/24).

En alfpru1:

– Está instalado MySQL con acceso para ambos nodos.
– Los recursos compartidos por NFS del repositorio y las configuraciones.
– WebSphere Application Server 7.0.17 (WAS7) con el Deploy Manager instalado.

En alfpru2:

– Websphere Application Server  7.0.17 (WAS7)

En la configuración del WAS7 se han creado dos servidores (alfpru1Cluster y alfpru2Cluster) que son miembros del cluster (alfrescoCluster).

La configuración del alfresco-global.properties es:

# Repository and indexes
#
dir.shared.root=/opt/alf343WAS7_data_cluster
dir.local.root=/opt/alf343WAS7_index_cluster
dir.root=${dir.shared.root}
dir.contentstore=${dir.shared.root}/contentstore
dir.contentstore.deleted=${dir.shared.root}/contentstore.deleted
dir.auditcontentstore=${dir.shared.root}/audit.contentstore
dir.indexes=${dir.local.root}/lucene-indexes
dir.indexes.backup=${dir.local.root}/backup-lucene-indexes
dir.indexes.lock=${dir.indexes}/locks
#
# Cluster config
#
alfresco.cluster.name=AlfpruClusterWAS7
alfresco.jgroups.defaultProtocol=TCP
alfresco.tcp.initial_hosts=alfpru1[7800],alfpru2[7800]
#
# Audit config
#
audit.enabled=false
audit.useNewConfig=false
#
# Tuning config
#
db.pool.initial=50
db.pool.min=50
db.pool.max=275
db.pool.idle=-1
hibernate.jdbc.fetch_size=150
#
# Tools config
#
jodconverter.officeHome=/usr/lib/openoffice.org3
jodconverter.portNumbers=8101
jodconverter.enabled=true
img.root=/usr
swf.exe=/usr/bin/pdf2swf
#
# Hibernate
#
hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect
#
# Lucene indexes config
#
index.recovery.mode=AUTO
index.tracking.cronExpression=0/5 * * * * ?
index.tracking.reindexLagMs=5000
#
# For WAS config
#
mbean.server.locateExistingServerIfPossible=false

El problema que puede presentarse ahora es que salga un error de este tipo:

[8/4/11 10:56:41:865 CEST] 0000001b ContextLoader E org.springframework.web.context.ContextLoader initWebApplicationContext Context initialization failed
                                 org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘propertyBackedBeanExporter’ defined in file [/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/alfpru1Cell01/Alfresco.ear/alfresco.war/WEB-INF/classes/alfresco/enterprise/repository-jmx-context.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.alfresco.enterprise.repo.management.subsystems.PropertyBackedBeanExporter]: Constructor threw exception; nested exception is org.alfresco.error.AlfrescoRuntimeException: 07040000 Failed to initialise JGroups channel:
   Cluster prefix:    AlfpruClusterWAS7
   App region:        org.alfresco.enterprise.repo.management.subsystems.PropertyBackedBeanExporter
   Channel:           org.jgroups.JChannel@48a648a6
   Configuration URL: file:/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/alfpru1Cell01/Alfresco.ear/alfresco.war/WEB-INF/classes/alfresco/jgroups/alfresco-jgroups-TCP.xml
[…]
Caused by: org.jgroups.ChannelException: failed to start protocol stack
[…]
Caused by: java.net.BindException: No available port to bind to

Este error es debido a que en el inicio de JGroups la dirección IP no es encontrada (o hay algún tipo de conflicto)

Para solucionar esto hay varias formas, pero la más sencilla es incluir el valor de jgroups.bind_addr para cada servidor directamente en los parámetros de inicio de la máquina virtual de Java. Para ello en WAS7 la introducimos en:

Servidores de aplicaciones de WebSphere/alfpru1Cluster/Java y gestión de procesos/Definición de proceso/Máquina virtual de java/Argumentos de JVM genéricos el valor: -Djgroups.bind_addr=alfpru1

Y en:

Servidores de aplicaciones de WebSphere/alfpru2Cluster/Java y gestión de procesos/Definición de proceso/Máquina virtual de java/Argumentos de JVM genéricos el valor: -Djgroups.bind_addr=alfpru2

Con esto se soluciona este error de manera sencilla y tendremos funcionando un clúster WAS7 + un clúster JGroups en Alfresco ECM

Para más información:
Alfresco Docs (Installing Alfresco in WebSphere)
Alfresco Docs (Configuring JGroups)
The JGroups 3.x Tutorial

Trucos para mantener un cluster en WebSphere 7 con Alfresco 3.4.x

Para instalar Alfresco ECM 3.4.x en WebSphere 7, lo mejor es seguir el siguiente enlace: http://docs.alfresco.com/3.4/index.jsp?topic=%2Fcom.alfresco.Enterprise_3_4_0.doc%2Ftasks%2Falf-websphere-install.html

Si además creamos un clúster de nodos en el mismo WebSphere además de la configuración para Alfresco ECM mediante JGroups, tenemos todavía algunas cosas que el servidor de aplicaciones no controla. Una de ellas es el control de los ficheros de configuración, comunmente llamados «ficheros del extension» o extension/alfresco.

Esto es fácil si compartimos mediante NFS estos ficheros. Imaginemos que tenemos dos servidores, alfpru1 y alfpru2, en ambos hay una instalación de WAS 7 en: /opt/IBM/WebSphere/AppServer

En esta existe un directorio llamado lib donde generalmente se crea un directorio llamado alfresco y dentro de este se introducen los ficheros de configuración. Bien, solo hay que exportar el recurso en la máquina o cabina, en este caso es la misma máquina alfpru1 y en la misma localización por defecto (lo suyo es tener evidentemente una cabina de discos para esto):

El fichero /etc/exports tendrá la siguiente línea:

/opt/IBM/WebSphere/AppServer/lib/alfresco       alfpru2(rw,sync)

Además deberá exportar el recurso compartido para el repositorio, etc.

Bien, ahora en alfpru2 creamos un directorio alfresco dentro de /opt/IBM/WebSphere/AppServer/lib y montamos el recurso como:

mount alfpru1:/opt/IBM/WebSphere/AppServer/lib/alfresco /opt/IBM/WebSphere/AppServer/lib/alfresco

Y listo!!… bueno, no del todo. Resulta que hay un fichero llamado alfresco-global.properties que está en el mismo nivel que el directorio alfresco. Como no queremos (ni podemos) exportar todo el directorio lib, lo que se puede hacer es mover este fichero dentro de lib y crear dos enlaces simbólicos, uno en cada servidor, de esta forma:

En alfpru1:

mv alfresco-global.properties ./alfresco
ln -s alfresco/alfresco-global.properties .

En alfpru2 (después de haber montado el recurso):

ln -s alfresco/alfresco-global.properties .

Para prácticamente toda la configuración vale que se compartan los ficheros, en algún caso excepcional necesitaremos otros valores, en el caso además de subsistemas podrémos realizar este truco también de forma que parte esté como recurso compartido y parte no.