Archivo de categoría: Alfresco

Avisos de Alfresco sobre saturación de la Cache (EHCache / Hibernate)

En algunas ocasiones, como migraciones, reindexaciones completas, etc. pueden salir determinados avisos como el siguiente:

… WARN [org.alfresco.repo.cache.TransactionalCache.org.alfresco.storeAndNodeIdTransactionalCache] Transactional update cache ‘org.alfresco.storeAndNodeIdTransactionalCache’ is full (10000).

Este tipo de avisos se refiere a que se ha llenado la memoria de intercambio o caché de segundo nivel (también llamada L2 Cache).

** SOLUCIÓN **

La solución pasa por agrandar el tamaño de esta tocando los parámetros del fichero cache-context.xml

Unos valores más acorde con situaciones de instalaciones en producción podrían ser los siguientes:

[…]
  
     
        
     
     
        
     
     
         org.alfresco.userToAuthorityTransactionalCache
     
     
         10000
     
  
[…]
  
     
        
     
     
        
     
     
     
         org.alfresco.personTransactionalCache
     
     
         25000
     
  
[…]
  
     
        
     
     
        
     
     
         org.alfresco.storeAndNodeIdTransactionalCache
     
     
         100000
     
  

[…]

Cada uno de estos valores hay que adaptarlo al número de elementos que tengamos, como son asignaciones de autorizaciones (roles en espacios de trabajo), usuarios y ficheros o documentos.

** CONSIDERACIONES **

Esto juega negativamente sobre el recolector de basura (GC) de la máquina Java (JVM) ya que tardará más en intentar recoger los objetos obsoletos.

Para esto podemos usar dos soluciones alternativas y parciales pero que impactarán sobre nuestra instalación, una es desactivar directamente la caché.

Para desconectar la caché de segundo nivel (L2 cache) podemos poner el siguiente valor en alfresco-global.properties:

hibernate.cache.use_second_level_cache=false

La otra forma de intentar resolver el tiempo de ralentización será hacer que se guarden los valores de la cache de forma más continuada. Para ello podemos reajustar los valores de los beans sessionSizeResourceInterceptor y sessionSizeResourceManager en el fichero hibernate-context.xml como sigue:

[…]
  
     
        
           
        
     
     
         10000
     
     
         100
     
  
  
     
        
     
     
         100
     
     
         100
     
     
         0
     
  
[…]

Esta última opción solo es recomendable para usarla durante una actualización, una reindexación completa, y cualquier operación que conlleve mucha carga de memoria de intercambio o cache. Una vez finalizada la operación, deberían restaurarse los valores originales.

No hay que olvidarse de hacer copia de estos ficheros para poder restaurarlos posteriormente a los valores originales.

Es recomendable además que se copien los ficheros ehcache-context.xml y hibernate-context.xml del directorio de despliegue al de configuración (extension) como custom-ehcache-context.xml y custom-hibernate-context.xml. De esta forma se pueden dejar los ficheros originales.

Más información y otras fuentes:

http://wiki.alfresco.com/wiki/Repository_Cache_Configuration
http://issues.alfresco.com/jira/browse/ETHREEOH-3294

La siguiente web es muy recomendable, tiene una serie de artículos sobre migraciones en los que se trata este tema.

http://alfrescoshare.wordpress.com/category/alfresco-dm/

Revisiones:

17/08/2011: En el caso de cluster, al usar ehcache-custom.xml en /extension, hay que modificar los valores ahí, no hace falta copiar el fichero ehcache.xml

Protección antivirus en Alfresco ECM (tercera parte)

Finalmente escribo esta tercera parte porque quedaron algunos puntos pendientes y me gustaría comentarlos.
El impacto que se produce al realizar llamadas a un comando o programa al sistema operativo desde Java es muy alto, solo estaría recomendable para sitios donde no haya una gran cantidad de subida de documentos o donde esta no sea  masiva.
La primera opción, la “desatendida” que escanea directamente el repositorio es menos intrusiva y puede ser programada además mediante una entrada en la crontab.
Existe una tercera forma dentro del sistema de escaneo bajo demanda, aunque realmente no es así, sino que es escaneado cuando el evento OnContentUpdate o OnContentRead es disparado. Esta forma es mediante el envío de la información al antivirus en forma de “data stream” o flujo de datos hacia un puerto determinado.
En este caso ClamAV puede ejecutarse en modo “demonio” con el comando clamd y podemos configurarlo para que escuche solicitudes de datos desde un puerto determinado de forma que enviaremos a este el documento para que sea escaneado y se nos devuelva un código de verificación.
Para configurar clamd se realiza en el fichero clamd.conf (generalmente en /usr/local/etc aunque dependerá de la distribución Linux que tengamos). 
De esta forma podemos cambiar algunos valores que por defecto están algo bajos:
/usr/local/etc/clamd.conf:
[…]
TCPSocket 3310
MaxConnectionQueueLength 30
StreamMaxLength 50M
MaxThreads 50
[…]
Ya solo queda modificar la clase para que envíe el flujo de datos hacia el puerto indicado y según nos devuelva el resultado así actuar. En mi caso he dejado la llamada directa al comando clamscan así como esta otra forma para que pueda seleccionarse la más adecuada según cada caso. 
También he realizado algunas modificaciones como que se pueda seleccionar qué evento se quiere que sea disparado (update, read, o ambos)  así como los nuevos valores de configuración necesarios.
La única acción que se realiza sigue siendo la asignación del aspecto “Infected”. Así se ha dejado para no realizar más acciones dentro de la propia lógica de la clase de detección y dejar este trabajo al propio Alfresco. Para ello solo habrá que crear una regla donde se necesite que contenga una acción sobre todo el contenido que se encuentre con este aspecto. Las acciones pueden ir desde enviar un mensaje de correo electrónico al propietario del documento, al administrador, etc. como mover el documento a un espacio de cuarentena o de infectados,… en fin, cada uno que elija las acciones según la política de seguridad y aplicación que se esté dando a Alfresco.
Para no dejar más código aquí, he creado un proyecto en Google Code para que pueda bajarse directamente el AMP o los fuentes de forma más fácil.
El proyecto está en: http://code.google.com/p/alfviral
El SVN es: “http://alfviral.googlecode.com/svn/trunk/ alfviral-read-only”
Si alguno quiere colaborar, como siempre, estaré encantado.  😉


Swithun Crowe ha realizado una «custom action» para poder escanear un documento a petición. Es otra idea más y esta es totalmente «bajo demanda» ya que es mediante una acción. Como es otra posibilidad se podría adaptar perfectamente a «Alfresco Virus Alert» y tenerla como otra posibilidad más dentro del marco de seguridad y protección de antivirus para Alfresco.

La entrada en la wiki es: http://wiki.alfresco.com/wiki/Antivirus

Protección antivirus en Alfresco ECM (segunda parte)

En la primera parte se vio como implementar una solución de antivirus escaneando todo el repositorio y que podía programarse por ejemplo en la “crontab” del sistema operativo.
Otra forma de realizar la detección de un virus que suba a Alfresco es mediante un escaneo “on-demand”, es decir, bajo demanda, y en este sentido cuando sea actualizado el contenido del documento. Dentro de las posibilidades que tenemos, de realizarlo de esta forma, podemos enviar un stream de datos del documento hacia el antivirus o bien ejecutar el propio antivirus pasándole exactamente el fichero a escanear. Ambas soluciones son posibles en ClamAV y en cualquiera de los dos casos el antivirus devuelve un código de error que será 0 si no hay infección y distinto si ha sido detectado, el fichero no existe, etc.
Por otra parte, Alfresco ECM disponde de las llamadas “policies” para ejecutar clases de Java o scripts de JavaScript cuando se produce algún “evento”. De todos los posibles tanto a nivel de contenido y de nodos, vamos a usar los siguientes:
Interface
Method
org.alfresco.repo.content.ContentServicePolicies
onContentUpdate
onContentRead
Usando OnContentUpdate y OnContentRead se puede lanzar la detección cuando sean leídos los documentos y/o cuando son actualizados. En el ejemplo se va a utilizar el evento OnContentUpdate para que cuando se realice una actualización de este (en el momento en el que se hace un COMMIT) se lance el antivirus y si este devuelve un código de error se añada un aspecto “infected” con dos propiedades, la fecha de detección y si ha sido “limpiado”.

Primero definimos los beans que usamos en el «behavior»: alfviral-behavior-context.xml

    
       
            true
        property>
       
           
                classpath:alfresco/extension/alfviral-behavior.properties
           
       
   
   
    <bean id="AlfViralBehavior" class="com.fegor.alfresco.behavior.OnUpdateReadScan"
        init-method=»init»>
       
           
       
       
           
       
       
           
       
       
            ${alfviral.command}
       
       
            ${dir.contentstore}
                  
    bean>

Seguidamente del fichero de propiedades: alfviral-behavior.properties

alfviral.command=/usr/bin/clamscan

Montamos el modelo de datos: alfviral-model-context.xml

       
   
       
           
                alfresco/extension/alfviralModel.xml
           
       
     

El modelo: alfviralModel.xml

     
   Alfresco Virus Alarm Model
   Fernando González Ruano (twitter://fegorara)
   1.0
  
     
     
  
  
     
  
   
       
            Infected
           
               
                    d:date
                    false
               
               
                    d:boolean
                    false
               
           
       
   

La parte para el cliente web del browser: web-client-config-custom.xml


  
     
        
        
     
  
  
     
        
     
  

El fichero de propiedades para las etiquetas: webclient.properties

ava_date=Fecha de detecciu00F3n
ava_clean=u00BFDesinfectado?

Y por último la clase java: OnUpdateReadScan.java

package com.fegor.alfresco.behavior;

import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.repo.content.ContentServicePolicies;
import org.alfresco.repo.policy.Behaviour;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.log4j.Logger;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.ContentReader;

/**
 * Integrates antivirus scanning documents for alfresco
 *
 * Implements the policies of «OnContentUpdate» and «OnContentRead».
 *
 * @author Fernando González Ruano (fegor)
 */
public class OnUpdateReadScan
    implements ContentServicePolicies.OnContentUpdatePolicy,
                ContentServicePolicies.OnContentReadPolicy
{
    private Logger logger = Logger.getLogger(OnUpdateReadScan.class);

    // behaviours
    private Behaviour onContentUpdate;
    private Behaviour onContentRead;
   
    // dependencias
    private PolicyComponent policyComponent;
    private ContentService contentService;
    private NodeService nodeService;
   
    // configuration
    private List command;
    private String store;
    private final String NAMESPACE_ALFVIRAL_CONTENT_MODEL = «alfviral.model»;
    private final QName ASPECT_INFECTED = QName.createQName(NAMESPACE_ALFVIRAL_CONTENT_MODEL, «infected»);
    private final QName PROP_INFECTED_DATE = QName.createQName(NAMESPACE_ALFVIRAL_CONTENT_MODEL, «date»);
    private final QName PROP_INFECTED_CLEAN = QName.createQName(NAMESPACE_ALFVIRAL_CONTENT_MODEL, «clean»);  
   
    Map aspectValues = new HashMap();

    // método de inicio
    public void init ()
    {
        if (logger.isDebugEnabled()) logger.debug(«Start OnUpdateReadScan.»);
       
        // crear behaviours
        this.onContentUpdate = new JavaBehaviour(this,
                «onContentUpdate»,
                NotificationFrequency.TRANSACTION_COMMIT);
       
        this.onContentRead = new JavaBehaviour(this,
                «onContentRead»,
                NotificationFrequency.TRANSACTION_COMMIT);
       
        // binding «policies»
        this.policyComponent.bindClassBehaviour(QName.createQName
                (NamespaceService.ALFRESCO_URI,
                «onContentUpdate»),
                «cm:content», this.onContentUpdate);
       
        this.policyComponent.bindClassBehaviour(QName.createQName
                (NamespaceService.ALFRESCO_URI,
                «onContentRead»),
                «cm:content», this.onContentRead);
    }
   
    @Override
    public void onContentUpdate (NodeRef nodeRef, boolean flag)
    {
        ContentReader contentReader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
       
        // full path of file
        String contentUrl = contentReader.getContentUrl();
        String contentPath = contentUrl.replaceFirst(«store:/», this.store);
       
        if (logger.isDebugEnabled())
        {
            logger.debug(«(Update) «+this.command+» «+contentPath);
        }
        else if (logger.isInfoEnabled())
        {
            logger.info(«(Update) Llamando al script de escaneo de virus para «+nodeRef.getId());
        }
       
        try
        {
            // execute command «antivir contentPath»
            this.command.add(contentPath);
            ProcessBuilder pb = new ProcessBuilder(this.command);
            Process process = pb.start();
           
            int intResult = process.waitFor();

            logger.debug(«(Update) Resultado del escaneo: «+intResult);
           
            // if result is not 0, file is infected
            if (intResult != 0)
            {
                logger.info(«ALERTA ** El fichero: «+contentReader.getContentUrl()+» está infectado. **»);
               
                // add aspect Infected is not assigned              
                if (!nodeService.hasAspect(nodeRef, this.ASPECT_INFECTED))
                {  
                    this.aspectValues.put(this.PROP_INFECTED_DATE, new Date());
                    this.aspectValues.put(this.PROP_INFECTED_CLEAN, false);               
                    nodeService.addAspect(nodeRef, this.ASPECT_INFECTED, this.aspectValues);
                   
                    // TODO Other actions… quarantine, delete, send email, etc.
                }
                else
                {
                    logger.debug(«Este fichero se detectó como infectado anteriormente.»);                   
                }
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
   
    @Override
    public void onContentRead (NodeRef nodeRef)
    {
        // TODO Actions for read content
    }
   
    // Setters…
    public void setPolicyComponent(PolicyComponent policyComponent)
    {
        this.policyComponent = policyComponent;
    }
   
    public void setContentService(ContentService contentService)
    {
        this.contentService = contentService;
    }
   
    public void setNodeService(NodeService nodeService)
    {
        this.nodeService = nodeService;
    }
   
    public void setStore(String store)
    {
        this.store = store;
    }
   
    public void setCommand(List command)
    {
        this.command = command;
    }   
}

A partir de aquí…

Esto es solo un ejemplo de la multitud de acciones y configuraciones que pueden realizarse en este sentido, por ejemplo, falta el código para el evento OnContentRead, falta alguna acción de mover los documentos infectados a algún espacio de cuarentena, avisar al administrador y al usuario de la detección, internacionalizar, etc. pero creo que es un buen punto de partida para que cada cual adapte esta solución a su manera. No os lo voy a dar todo hecho ¿verdad?… 😉

Finalmente dos capturas de pantalla:


Un extracto del log (poniendo en el log4j.properties el valor log4j.logger.com.fegor=debug):

 

10:44:16,141 User:admin DEBUG [alfresco.behavior.OnUpdateReadScan] (Update) [/usr/local/bin/clamscan, /home/alfresco/enterprise/alf_data/contentstore/2010/12/30/8/3/30a7961f-cdef-4410-9ba0-ca94a8542d03.bin, /home/alfresco/enterprise/alf_data/contentstore/2010/12/30/10/42/4a6a4884-c14e-4b24-ae22-c1e2a9839593.bin, /home/alfresco/enterprise/alf_data/contentstore/2010/12/30/10/42/4a6a4884-c14e-4b24-ae22-c1e2a9839593.bin] /home/alfresco/enterprise/alf_data/contentstore/2010/12/30/10/44/b9b5c69c-cff5-400e-9f63-4888ca745749.bin
10:44:20,125 User:admin DEBUG [alfresco.behavior.OnUpdateReadScan] (Update) Resultado del escaneo: 1
10:44:20,126 User:admin INFO  [alfresco.behavior.OnUpdateReadScan] ALERTA ** El fichero: store://2010/12/30/10/44/b9b5c69c-cff5-400e-9f63-4888ca745749.bin está infectado. **

Y algo de bibliografía que podéis consultar:

Libro: Alfresco Developer Guide
Autor: Jeff Potts
ISBN: 978-1-847193-11-7

Web de Jeff Potts: http://ecmarchitect.com/
Wiki de Alfresco: http://wiki.alfresco.com/wiki/Policy_Component
Web de ClamAV: http://www.clamav.net/lang/en/

Protección antivirus en Alfresco ECM (primera parte)

Alfresco ECM no viene con antivirus de serie, al igual que otras aplicaciones y programas como puede ser OpenOffice.org, etc. dejando esta tarea al sistema operativo y las soluciones antivirus elegidas.
La solución de usar un antivirus directamente sobre Alfresco plantea varias soluciones cada una con sus pros y sus contras así como el impacto en el sistema que puede ser mayor o menor.
.
Se pueden plantear por tanto varias soluciones de escaneo con los documentos subidos a Alfresco, que pueden ser:
1. Escaneo del repositorio localizando el fichero infectado directamente y dejando a posteriori la localización del NodeRef.
2. Escaneo bajo demanda al subir un documento, haciendo para ello que Alfresco ECM redirija el contenido hacia un antivirus que permita la lectura de un stream de datos.
Ambas soluciones son viables en Alfresco aunque su función es distinta, en la primera se escanea todo el repositorio, dejándose generalmente para tareas nocturnas o de fin de semana y aseguran tener un repositorio saneado con el tiempo. Esto causa impacto en los discos que cada cierto tiempo tienen que realizar la tarea de principio a fin y que puede ser muy larga dependiendo del tamaño del repositorio.
La segunda solución es limpia y permite que cualquier documento subido a Alfresco ECM haya sido previamente escaneado, bloqueando si es necesaria, su subida si se detecta un código maligno. Esta solución impacta directamente a la hora de la subida del documento pero nos asegura que los archivos subidos estén limpios sin tener que ser examinados posteriormente.
Una solución muy completa es la de unir ambas soluciones de forma que siempre sean escaneados los documentos al ser subidos y creados en Alfresco ECM y a su vez, que cada cierto tiempo se compruebe la “salud” de los documentos pasando un escaneo completo ya que además se escanearán con la última actualización del antivirus lo que puede detectar documentos con virus que inicialmente no fueron detectados.
— Una primera solución —
Para la primera solución, a su vez, se puede abordar desde distintas perspectivas, bien desde un plano más externo a Alfresco ECM y otro directamente desarrollando parte de la solución como una extensión de este.
En cualquier caso hay que elegir una solución antivirus que pueda servirnos para estos propósitos. En mi caso he elegido la solución ClamAV ya que permite muchas formas de interactuar, es OpenSource y podemos usarlo en varias plataformas (Linux, BSD, AIS, Solaris, etc.)
Para instalar ClamAV solo hay que instalarlo vía apt-get/aptitude, yum, etc. o bien bajando el fichero directamente y compilándolo, generalmente con la secuencia:
./configure
make
make install
Una vez instalado (en el servidor, se entiende) podemos hacer uso del comando “clamscan” para escanear el repositorio. (NOTA: No es objetivo de este post la configuración de clamav, freshclam, etc.)
El uso de clamscan es muy sencillo, básicamente hay que poner las opciones necesarias y el directorio/fichero a escanear. Aquí vamos a usar las opciones de visualizar solo los ficheros infectados y que además sea de forma recursiva.
Un ejemplo:
clamscan -i -r /alfresco/alf_data/
Bien, hasta aquí correcto, nos listará los virus encontrados, pero ahora necesitamos saber a qué documento de alfresco corresponde en su entorno. Como sabemos, en Alfresco, ni siquiera el nombre del documento es correcto para poder localizarlo, en todo caso podría ser si conocemos la ruta completa, aunque no sería lo mejor; pero si podemos usar el UUID o también llamado nodeRef.
Aquí podemos usar también varias formas, bien haciendo llamadas RESTful/SOAP a WebServices o WebScripts de Alfresco ECM en el que hay que extenderlo para poder realizar las llamadas necesarias y que este nos devuelva los valores o bien realizar las acciones que necesitamos; o usar directamente consultas SQL a la base de datos utilizada. En este primer caso vamos a usar una consulta SQL hacia nuestra instalación en MySQL.
La consulta podría ser algo asi:

SELECT alf_node.uuid 
    FROM alf_node_properties
        INNER JOIN alf_node
            ON alf_node.id = alf_node_properties.node_id
        INNER JOIN alf_qname
            ON alf_qname.id = alf_node_properties.qname_id
WHERE alf_node_properties.node_id =
(SELECT alf_node_properties.node_id 
    FROM alf_content_url
                INNER JOIN alf_content_data
                        ON alf_content_url.id = alf_content_data.content_url_id
        INNER JOIN alf_node_properties
            ON alf_content_data.id = alf_node_properties.long_value
        INNER JOIN alf_qname
            ON alf_qname.id = alf_node_properties.qname_id
    WHERE alf_content_url.content_url = ‘${FUID}’
    AND alf_qname.local_name = ‘content’)
AND alf_qname.local_name = ‘name’;
 

Donde ${FUID} es la localización física del fichero en el contentstore o File Unique IDentifier, lo que Alfresco ECM llama normalmente contentUrl.
Bien, entonces, ¿Cómo podríamos hacer para que si un fichero es localizado con un virus, nuestro sistema Linux realice una llamada a Alfresco para que, por ejemplo lo renombre, ponga un aviso en el título, envíe correos electrónicos, etc.?
Para esto vamos a usar además un comando más, el comando wget (o curl si se prefiere) que puede realizar peticiones GET a un servidor para poder llamar a Alfresco ECM via RESTful y realizar una llamada al WebScript necesario.
Lo primero es el script que usaremos dentro del cron del sistema operativo (en este caso realizado para Linux CentOS con bash):
——————– alfviral.sh
#!/bin/bash

# Variables de configuración
#

ALFUSER=admin
ALFPASSWD=admin
ALFRESCO_URL=http://localhost:8080/alfresco
DIR_ROOT=/home/alfresco/alfresco-enterprise-3.3.4/alf_data
USERNAME=alfresco
PASSWD=alfresco
DATABASE=alfresco_enterprise_334
HOST=localhost
PORT=3306

PROG=$0
PROGDIR=`dirname «$PROG»`

SCANRES_FILE=scanres.txt
NODEREFS_FILE=noderefs.txt
DOCNAMES_FILE=docnames.txt

# Crea lista de ficheros infectados
#
echo «Creando lista de ficheros infectados…»
rm -f ${PROGDIR}/${SCANRES_FILE} 2>/dev/null
CONTENTSTORE=${DIR_ROOT}/contentstore
clamscan -i -r ${CONTENTSTORE} | awk -F: ‘$1~/.bin/{print «store:/»$1}’ | sed s:${CONTENTSTORE}::g >${PROGDIR}/${SCANRES_FILE}

if [ ! -s ${PROGDIR}/${SCANRES_FILE} ]
then
    echo «No hay ficheros infectados.»
    exit 0
fi

# Crea lista de NodeRefs de los ficheros
#
echo «Creando referencias NodeRefs de los FUID…»
rm -f ${PROGDIR}/${NODEREFS_FILE} 2>/dev/null
for FUID in $(cat ${PROGDIR}/${SCANRES_FILE})
do
    mysql -u${USERNAME} -p${PASSWD} -D${DATABASE} -h${HOST} -P${PORT} –skip-column-names –raw –silent >>${PROGDIR}/${NODEREFS_FILE} <
SELECT alf_node.uuid 
    FROM alf_node_properties
        INNER JOIN alf_node
            ON alf_node.id = alf_node_properties.node_id
        INNER JOIN alf_qname
            ON alf_qname.id = alf_node_properties.qname_id
WHERE alf_node_properties.node_id =
(SELECT alf_node_properties.node_id 
    FROM alf_content_url
                INNER JOIN alf_content_data
                        ON alf_content_url.id = alf_content_data.content_url_id
        INNER JOIN alf_node_properties
            ON alf_content_data.id = alf_node_properties.long_value
        INNER JOIN alf_qname
            ON alf_qname.id = alf_node_properties.qname_id
    WHERE alf_content_url.content_url = ‘${FUID}’
    AND alf_qname.local_name = ‘content’)
AND alf_qname.local_name = ‘name’;
q
STOP
done

if [ ! -s ${PROGDIR}/${NODEREFS_FILE} ]
then
    echo «Â¡No se han encontrado referencias a los ficheros!»
    exit 1
fi

# Lanza las llamadas a Alfresco hacia el webscript
#
echo «Llamando a Alfresco…»
rm -f ${PROGDIR}/${DOCNAMES_FILE} 2>/dev/null
ALF_TICKET=`curl «http://localhost:8080/alfresco/service/api/login?u=${ALFUSER}&pw=${ALFPASSWD}» | grep TICKET_ | sed ‘s:::g’ | sed ‘s:::g’`
for NODEREF in $(cat ${PROGDIR}/${NODEREFS_FILE})
do
    curl «${ALFRESCO_URL}/service/protect/alfviral?nref=${NODEREF}&alf_ticket=${ALF_TICKET}» >>${PROGDIR}/${DOCNAMES_FILE}
    echo «» >>${PROGDIR}/${DOCNAMES_FILE}
done——————– alfviral.sh

Y en segundo lugar crear un WebScript que realice las tareas necesarias:
——————– alfviral.get.desc.xml
<webscript>  <shortname>Alfresco Virus Alertshortname>
  <description>Alfresco Virus Alertdescription>
   <url>/protect/alfviral?nref={nref}url>
   <format default=»text»>extension</format>
   <authentication>user</authentication>
   <transaction>required</transaction>
webscript>
——————– alfviral.get.desc.xml

——————– alfviral.get.js
// chequeo de parámetros
if (args.nref == undefined || args.nref.length == 0)
{
    status.code = 400;
    status.message = «Es necesario indicar el nref.»;
    status.redirect = true;
}
else
{
    // buscar el documento por su nodeRef
    var nodes = search.luceneSearch(«ID:»workspace://SpacesStore/» + args.nref + «»»);

    // renombrar el documento
    var name_infected = «»;
    name_infected = nodes[0].name;
    if (name_infected.indexOf(«_INFECTADO») == -1)
    {
        nodes[0].name = name_infected + «_INFECTADO»;
        nodes[0].save();
        if (logger.isLoggingEnabled())
            logger.log(«El documento: » + nodes[0].name + » ha sido renombrado por estar infectado.»);
    }
    
    model.name_infected = nodes[0].name;
}

——————– alfviral.get.js

——————– alfviral.get.text.ftl
${name_infected}
——————– alfviral.get.text.ftl

——————– alfviral.get.html.400.ftl

${status.message}
body>
html>

——————– alfviral.get.html.400.ftl
Poniendo la llamada en nuestra crontab podemos escanear periódicamente nuestro repositorio y actuar en consecuecia.
En este caso, simplemente se han renombrado los documentos infectados, como puede verse en la imagen:

Para la segunda entrega seguiremos abordando nuevas posibilidades para mantener «sano» nuestro repositorio.

¡Ah!, y FELIZ NAVIDAD A TODOS

Alfresco Community 3.4.a y iBatis

Hoy he visto la noticia de la liberación de alfresco Community en su versión 3.4.a (lo de la letra «a» es debido a la nueva nomenclatura de que adopta Alfresco para su versión Community).

¿Donde he visto la noticia?, en los foros de Alfresco evidentemente y en el blog de Toni de la Fuente (blyx.com). Para saber más sobre esta nueva versión os remito a su blog: «Liberada Alfresco Community versión 3.4.a»

Una de las cosas que más me ha gustado ha sido la inclusión de iBatis y la eliminación de Hibernate, evidentemente solo para la parte del repositorio ya que jBPM usa también como capa de persistencia esta última y no puede eliminarse (todavía).

iBatis es un marco de trabajo para usar persistencia ayudado por SGBDs (Sistemas Gestores de Bases de Datos) al igual que Hibernate.

iBatis está dentro del proyecto Apache y lo que más destaca es su facilidad para «mapear» clases de Java y sus respectivos comandos en SQL, o sea, mapeo de campos y paso de parámetros principalmente. Personalmente creo que es un acierto que Alfresco incluya a iBatis en su sistema (al igual que otros productos que iré comentando en otras publicaciones).

Además cuenta con una herramienta llamada ibator y que no es más que un generador de código para ayudar al programador a obtener:

  • Ficheros SqlMap XML
  • Clases Java para mapear los campos y claves principales
  • Clases Java que usan los objetos DAO (opcionalmente)

Esta herramienta contiene también un plugin para Eclipse.

Si queréis saber más sobre el proyecto iBatis podéis ir a su web oficial: http://ibatis.apache.org/

Subir documentos por FTP en Alfresco

En determinadas ocasiones necesitamos automatizar la subida de documentos a Alfresco desde algún filesystem. Para ello podemos usar los distintos protocolos de los que nos ofrece, si bien, en este caso, si es algo sencillo en intranet y no necesitamos para ello realizarlo mediante CIFS o WebDAV, podemos recurrir al siempre sencillo protocolo FTP.

Para además controlar si el o los documentos se han subido correctamente podemos usar un script y ayudarnos del comando «curl» que está para Linux, Solaris, Unix, Windows, etc.

Este es un ejemplo de script que controla la subida de un documento y que podéis adaptar a vuestras necesidades.

#!/bin/bash
FICHERO=prueba.txt
curl -u admin:admin -T $FICHERO ftp://localhost:1121/Alfresco/$FICHERO >/dev/null 2>&1
if [ $? -eq 0 ]; then
        echo «El fichero se ha subido correctamente.»
else
        echo «ERROR en la subida del fichero.»
fi

 

Compilar Alfresco Community en MacOS X


Instalar Eclipse:

Lo primero es instalar Eclipse de http://www.eclipse.org siguiendo el instalador para MacOS X.

Instalar Alfresco desde el instalador:

Lo segundo, como recomendación para copiar todas las utilidades que necesitamos sin necesidad de bajarselas de las respectivas webs, es instalar la versión de Alfresco con instalador que haya para MacOS X desde http://www.alfresco.com

Luego, copiamos el directorio tomcat que hay en el Alfresco instalado en la ubicación donde queremos dejar el resultado de la compilación, en mi caso en /Users/fegor/Documents/workspace/tomcat

Para la configuración podemos aprovechar para copiar un fichero de configuración de ejemplo que es desplegado en el primer arranque (por cierto, en la instalación se puede usar la base de datos por defecto que es Derby ya que la que usaremos posteriormente es MySQL)

cd /Users/fegor/Documents/workspace/tomcat
cp webapps/alfresco/WEB-INF/classes/alfresco-global.properties.sample shared/classes/alfresco-global.properties

Se modifica este fichero con los valores correspondientes:

dir.root=/Users/fegor/Documents/workspace/tomcat/alf_data
db.username=alfresco
db.password=alfresco
db.driver=org.gjt.mm.mysql.Driver
db.url=jdbc:mysql://localhost/alfresco

Y se añaden las líneas para localizar las diferentes herramientas necesarias (hay que verificar que los paths son los correctos)

ooo.exe=/Applications/OpenOffice.org.app
ooo.user=${dir.root}/oouser
img.root=/Users/fegor/Documents/workspace/tomcat/ImageMagick-6.5.9
swf.exe=/Users/fegor/Documents/workspace/tomcat/pdf2swf

Instalando OpenOffice:

Si se va a utilizar OpenOffice para más cosas que la conversión de documentos en Alfresco, lo mejor es instalarlo de forma estándar descargándolo de http://porting.openoffice.org/mac/

Instalando pdf2swf:

La forma que he visto más sencilla es instalar la versión full para MacOS X (por ejemplo la versión 3.1 SP2 tiene instalador para MacOS X) y luego copiar el ejecutable a nuestra compilación:

cp /Users/fegor/Alfresco/bin/pdf2swf /Users/fegor/Documents/workspace/tomcat

Instalando ImageMagick:

Para la instalación de ImageMagick se puede copiar también el directorio de la instalación full de Alfresco o bien descargarlo de http://www.imagemagick.org/script/binary-releases.php

En esta página existen dos formas de instalarlo, una mediante MacPorts (http://www.macports.org/) y otra bajándose directamente el paquete en tar.gz (http://www.imagemagick.org/download/binaries/ImageMagick-x86_64-apple-darwin10.2.0.tar.gz). He preferido bajar el paquete, descomprimirlo y ponerlo directamente dentro de la instalación de tomcat (Alfresco) ya que solo va a usarse para esto.

Por tanto:

tar xvzf ImageMagick-x86_64-apple-darwin10.2.0.tar.gz
mv ImageMagick-6.5.9/ /Users/fegor/Documents/workspace/tomcat/

Instalando MySQL para Mac:

Se puede descargar de la propia http://www.mysql.com y usar las instrucciones de instalación siguientes: http://dev.mysql.com/doc/refman/5.0/es/mac-os-x-installation.html

Una vez instalada y arrancada hay que crear la base de datos alfesco con usuario alfresco y password alfresco.

mysqladmin -u root create alfresco
mysql -u root -e «grant all on alfresco.* to ‘alfresco’@’localhost.localdomain’ identified by ‘alfresco’ with grant option;»
mysql -u root -e «grant all on alfresco.* to ‘alfresco’@’localhost’ identified by ‘alfresco’ with grant option;»

Bajar los fuentes de Alfresco:

Se puede seguir el siguiente enlace en caso de problemas, http://wiki.alfresco.com/wiki/Alfresco_SVN_Development_Environment y en concreto para eclipse el siguiente http://wiki.alfresco.com/wiki/Alfresco_on_Eclipse

Instalar Sysdeo para usar (arrancar y parar) los servidores de aplicaciones tomcat, desde http://www.eclipsetotale.com/tomcatPlugin/tomcatPluginV321.zip.

Solo hay que descomprimirlo dentro del directorio plugins, en mi caso en /Applications/eclipse/plugins

Ejecutar eclipse y configurar en Eclipse -> Preferences -> Tomcat el lugar donde está el tomcat, la JVM y añadir los siguientes parámetros a la JVM (JVM Settings):

-server
-Xss256k
-Xms256m
-Xmx512m
-XX:NewSize=128M
-XX:MaxPermSize=128M

Y en Classpath: hay que introducir el camino a las classes de tomcat:

/Users/fegor/Documents/workspace/tomcat/shared/classes

Ahora configuramos Ant desde Eclipse -> Preferencias -> Ant -> Runtime -> Properties

Y añadir las siguientes propiedades:

env.TOMCAT_HOME = /Users/fegor/Documents/workspace/tomcat
env.APP_TOMCAT_HOME = /Users/fegor/Documents/workspace/tomcat
env.VIRTUAL_TOMCAT_HOME = /Users/fegor/Documents/workspace/tomcat/virtual-tomcat

Instalar Subclipse:

En nuestro eclipse: Help -> Install new software… (botón Add)

Name: Subclipse 1.6.x (Eclipse 3.2+)
Location: http://subclipse.tigris.org/update_1.6.x

Y proceder a marcar las casillas e instalar (botón Next…).

Configurar el proyecto de Alfresco en SVN:

1) Ir a File -> New -> Project
2) Seleccionar el asistente SVN y «Checkout Projects with Subversion»
3) En «Create a new repository location»
4) Seleccionar la URL: «svn://svn.alfresco.com» (*)
5) Por último la carpeta Alfresco y pulsar Finish.

Importar el proyecto Alfresco siguiendo los siguientes pasos:

1) File -> Import
2) General -> Existing Projects Into Workspace -> Next
3) Seleccionar HEADrootproyecto
4) Finish

Crear el proyecto Ant:

1) Seleccionar New -> Project. Bajo «General» elegir «Project» y en el nombre poner «ant»
2) Pulsar botón derecho del ratón en el proyecto y seleccionar New -> File
3) Cuando aparezca el nuevo diálogo, pulsar en el botón de Advanced y seleccionar «Link to file in the file system»
4) Ir al fichero build.xml localizado en el directorio HEADroot
5) Para construir el proyecto, ponerse encima de Build.xml con el botón derecho del ratón y seleccionar Run As… -> Ant Build

Nota: Si se reciben errores en la compilación, hay que elegir java-6-sun en lugar de java-6-opensdk en Preferences -> Java -> Installed JREs.

…y eso es todo. Para muestra «un botón», digo unas imágenes 😉

Resultado de la compilación:


Y la versión resultante:

(*) Me confundí con la URL, la siguiente funciona correctamente: http://svn.alfresco.com/repos/alfresco-open-mirror/alfresco

Crear una acción para enviar documentos a un ContentStore distinto

Tras la conversación con Toni (blyx) sobre el aspecto storeSelector en Alfresco ECM 3.2 y las distintas posibilidades que podemos usar para automatizar el movimiento de documentos. Se me ocurre una que puede ser crear una acción para que mueva determinados documentos a, por ejemplo, una NAS, pero de forma interactiva.

Lo primero es configurar el nuevo «storeNAS» donde estará montada la NAS:

Fichero: storeNAS-content-store-selector-context.xml (siempre dentro de alfresco/extension)



<?xml version=’1.0′ encoding=’UTF-8′?>

<!DOCTYPE beans PUBLIC ‘-//SPRING//DTD BEAN//EN’ ‘http:// www.springframework.org/dtd/spring-beans.dtd’>

<!– Define the new file stores —>

<beans>

<bean id=»fileContentStoreNAS» class=»org.alfresco.repo.content.filestore.FileContentStore»>

<constructor-arg>

<value>${dir.root}/storeNAS</value>

</constructor-arg>

</bean>

<!– Declare the mapping between store names and store instances —>

<bean id=»storeSelectorContentStore» parent=»storeSelectorContentStoreBase»>

<property name=»defaultStoreName»>

<value>default</value>

</property>

<property name=»storesByName»>

<map>

<entry key=»default»>

<ref bean=»fileContentStore» />

</entry>

<entry key=»storeNAS»>

<ref bean=»fileContentStoreNAS» />

</entry>

</map>

</property>

</bean>

<!– Point the ContentService to the ‘selector’ store —>

<bean id=»contentService» parent=»baseContentService»>

<property name=»store»>

<ref bean=»storeSelectorContentStore» />

</property>

</bean>

<!– Add the other stores to the list of stores for cleaning —>

<bean id=»eagerContentStoreCleaner» class=»org.alfresco.repo.content.cleanup.EagerContentStoreCleaner» init-method=»init»>

<property name=»eagerOrphanCleanup» >

<value>${system.content.eagerOrphanCleanup}</value>

</property>

<property name=»stores» >

<list>

<ref bean=»fileContentStore» />

<ref bean=»fileContentStoreNAS» />

</list>

</property>

<property name=»listeners» >

<ref bean=»deletedContentBackupListeners» />

</property>

</bean>

</beans>

Ahora necesitamos configurar nuestra acción, eso lo realizamos en el fichero web-client-config-custom.xml y además de la acción configuramos también el aspecto:

Fichero: web-client-config-custom.xml



<config evaluator=»aspect-name» condition=»cm:storeSelector»>

<property-sheet>

<show-property name=»cm:storeName» />

</property-sheet>

</config>



<config evaluator=»string-compare» condition=»Action Wizards»>

<aspects>

<aspect name=»cm:storeSelector»/>

</aspects>

</config>

<config>

<actions>

<!– Launch Add Aspect Dialog —>

<action id=»add_store»>

<label>Add Store</label>

<image>/images/icons/add.gif</image>

<action>dialog:addStore</action>

</action>

<!– Add action to more actions menu for each space —>

<action-group id=»space_browse_menu»>

<action idref=»add_store» />

</action-group>

</actions>

</config>

Para crear nuestra propia acción asignamos al mismo fichero web-client-config-custom.xml la acción con la llamada al script:

<config>

<actions>

<action id=»sendStoreNAS»>

<label>Enviar a NAS</label>

<image>/images/icons/new_edition_icon.gif</image>

<script>/Company Home/Data Dictionary/Scripts/ContentStoreNAS.js</script>

<params>

<param name=»id»>#{actionContext.id}</param>

</params>

</action>

<action-group id=»document_browse_menu»>

<action idref=»sendStoreNAS» />

</action-group>

</actions>

</config>

Y por último el código en jscript que subiremos a Empresa/Diccionario de datos/Scripts:

Fichero: ContentStoreNAS.js

var document = search.findNode( «workspace://SpacesStore/» + args[«id»]);

document.addAspect(«cm:storeSelector»);

document.properties[«cm:storeName»] = «storeNAS»;

document.save();

var ret = «<html>» +

«<title></title>» +

«<body>» +

«<script>history.back();</script>» +

«</body>» +

«</html>«;

ret;

Ahora para cada documento podemos enviarlo al ContentStore desde el menú emergente de más acciones.



Todavía queda alguna forma más para tratar los documentos con distintos almacenes, según las necesidades y requisitos, todo es cuestión de ponerse…

Usando WebDrive como cliente WebDAV con Alfresco

De muchos es sabido que no todos los protocolos se comportan igual ante servidores que ofertan sus servicios sobre clientes heterogéneos. Es el caso del cliente de Windows para WebDAV y sobre todo cuando se accede a servidores de WebDAV como el ofrecido por Alfresco ECM.

Uno de los problemas de crear un acceso vía WebDAV desde Windows XP a Alfresco ECM 3.2 es que, por ejemplo, cuando se intenta abrir un documento en formato PDF desde el mismo recurso compartido y el lector por defecto es el Acrobat Reader, se produce un error.

… creamos el acceso desde el cliente WebDAV de Windows:

… abrimos directamente el documento PDF… ¡error!


Parece ser que el problema puede ser de Alfresco, del cliente WebDAV de Windows o del propio Acrobat Reader… bien, para descartar que sean el primero y el tercero, lo mejor es buscar un programa de terceros que haga de cliente WebDAV y probar sobre este. Para ello he descargado el programa WebDrive (http://www.webdrive.com/) que permite crear unidades y conectar por WebDAV, sFTP, FTP, etc.

Una vez descargado de la web (la versión trial/free) e instalado (siguiente, siguiente, etc.) se procede a crear una unidad con los datos de conexión con el servicio WebDAV de Alfresco:

… volvemos a probar abrir el documento PDF desde este nuevo recurso y… ¡voilá!


A priori, parece que este programa se comporta mejor con servidores WebDAV, al menos con el ofrecido por Alfresco ECM.

Nota: He probado también con tildes y eñes y no ha dado error al acceder.