Tag Archives: Alfresco

Transformar ficheros RAW en Alfresco

Como ya sabemos, Alfresco tiene muchas posibilidades de manipulación y transformación de ficheros/documentos. Una de las cosas más flexibles es el uso de transformadores que puden ser de dos tipos básicamente:

  1. Llamadas directas a algún comando o utilidad del sistema
  2. Desarrollar una clase Java que realice la transformación

Aquí vamos a ver la primera para utilizar la transformación de fotografías tomadas en formato RAW (o bruto como generalmente se llama) a TIFF y a JPEG. Actualmente hago fotos con Nikon y Olympus y me interesa pasar el formato NEF (por poner el de Nikon) a TIFF y a JPEG. ImageMagick puede hacerlo pero me ha dado problemas tanto en el resultado (no se pueden ver) como en la propia conversión por lo que voy a usar otra utilidad que existe para Linux, Mac OS X y Windows.

Esta utilidad se llama dcraw y podemos bajarla de http://www.cybercom.net/~dcoffin/dcraw/ para el sistema operativo que tengamos. En este caso voy a utilizar la que hay para Windows compilada con MingW desde la dirección http://www.rawness.es/dcraw/?lang=es

Una vez instalada o descomprimida en el directorio que nos guste obtendremos el programa ejecutable dcraw.exe

Bien, ahora a configurar el transformador…

Toda la configuración se basa en un bean llamado RuntimeExecutableContentTransformer para versiones anteriore a la 3.2 de Alfresco o RuntimeExecutableContentTransformeWorker para las siguientes. A su vez este bean contiene las siguientes propiedades principales:

  • transformCommand: Define el comando y parámetros usando variables como ${source} y ${target}
  • checkCommand: Chequea si la sintaxis es correcta y si ocurre un error también se puede definir con errorCodes. Cuando no está disponible el método getReliability devuelve 0:0 este método se usa para utilizar el transformador más eficiente en caso de que haya varios definidos que conviertan de la misma fuente al mismo destino.
  • explicitTransformations establece el tipo MIME fuente y destino de forma explícita.

Creamos el fichero nef-tiff-jpeg-transform-context.xml:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
     <bean id="transformer.worker.NEFtoTIFF" class="org.alfresco.repo.content.transform.RuntimeExecutableContentTransformerWorker">
         <property name="checkCommand">
             <bean class="org.alfresco.util.exec.RuntimeExec">
                 <property name="commandMap">
                     <map>
                         <entry key=".*">
                             <value>${dcraw.exe} -i ${dcraw.home}/test.nef</value>
                         </entry>
                     </map>
                 </property>
                 <property name="errorCodes">
                     <value>1</value>
                 </property>
             </bean>
         </property>
         <property name="transformCommand">
             <bean class="org.alfresco.util.exec.RuntimeExec">
                 <property name="commandMap">
                     <map>
                         <entry key="Linux.*">                    
                             <value>${dcraw.exe} -T '${source}' '${target}'</value>
                         </entry>
                         <entry key="Mac OS X">
                             <value>${dcraw.exe} -T '${source}' '${target}'</value>
                         </entry>                    
                         <entry key="Windows.*">                
                             <value>${dcraw.exe} -T "${source}" "${target}"</value>
                         </entry>
                     </map>
                 </property>
                 <property name="waitForCompletion">
                     <value>true</value>
                 </property>
             </bean>
         </property>
         <property name="explicitTransformations">
             <list>
                 <bean class="org.alfresco.repo.content.transform.ExplictTransformationDetails" >
                     <property name="sourceMimetype"><value>image/x-nikon-nef</value></property>
                     <property name="targetMimetype"><value>image/tiff</value></property>
                 </bean>
             </list>
         </property>
         <property name="mimetypeService">
             <ref bean="mimetypeService"/>
         </property>
     </bean>
     <bean id="transformer.NEFtoTIFF" class="org.alfresco.repo.content.transform.ProxyContentTransformer" parent="baseContentTransformer">
         <property name="worker">
             <ref bean="transformer.worker.NEFtoTIFF"/>
         </property>
     </bean>
     <bean id="transformer.complex.Nef.Jpg"
         class="org.alfresco.repo.content.transform.ComplexContentTransformer"
         parent="baseContentTransformer" >
         <property name="transformers">
             <list>
                 <ref bean="transformer.NEFtoTIFF" />
                 <ref bean="transformer.ImageMagick" />
             </list>
         </property>
         <property name="intermediateMimetypes">
             <list>
                 <value>image/tiff</value>
             </list>
         </property>
     </bean>
 </beans>
Como vemos hay además otro bean definido de la clase ComplexContentTransformer, este sirve para realizar transformaciones a partir de otras, es decir, si no podemos transformar una imagen del formato NEF al formato JPEG pero si podemos realizar la transformación de NEF a TIFF y de TIFF a JPEG podemos configurarlo a través de este bean en el que hay que poner los transformadores y el mimetype intermedio de la transformación.
 Con esto ya podemos transformar imágenes tomadas directamente en RAW (NEF en el caso de Nikon) a TIFF y a si vez y mediante ComplexContentTransformer directamente a JPEG.

Ahora con dcraw.exe…

Esta utilidad de línea de comandos al igual que convert.exe de ImageMagick tiene la siguiente sintaxis:

C:Usersfernando.gonzalezworkspaceCommonMingW-release-orig-x32>dcraw.exe
Raw photo decoder “dcraw” v9.06

by Dave Coffin, dcoffin a cybercom o net
Usage:  dcraw.exe [OPTION]… [FILE]…
-v        Print verbose messages
-c        Write image data to standard output
-e        Extract embedded thumbnail image
-i        Identify files without decoding them
-i -v     Identify files and show metadata
-z        Change file dates to camera timestamp
-w        Use camera white balance, if possible
-a        Average the whole image for white balance
-A Average a grey box for white balance
-r Set custom white balance
+M/-M     Use/don’t use an embedded color matrix
-C   Correct chromatic aberration
-P Fix the dead pixels listed in this file
-K Subtract dark frame (16-bit raw PGM)
-k   Set the darkness level
-S   Set the saturation level
-n   Set threshold for wavelet denoising
-H [0-9]  Highlight mode (0=clip, 1=unclip, 2=blend, 3+=rebuild)
-t [0-7]  Flip image (0=none, 3=180, 5=90CCW, 6=90CW)
-o [0-5]  Output colorspace (raw,sRGB,Adobe,Wide,ProPhoto,XYZ)
-d        Document mode (no color, no interpolation)
-D        Document mode without scaling (totally raw)
-j        Don’t stretch or rotate raw pixels
-W        Don’t automatically brighten the image
-b   Adjust brightness (default = 1.0)

-gSet custom gamma curve (default = 2.222 4.5)

-q [0-3]  Set the interpolation quality
-h        Half-size color image (twice as fast as “-q 0”)
-f        Interpolate RGGB as four colors
-m   Apply a 3×3 median filter to R-G and B-G
-s [0..N-1] Select one raw image or “all” from each file
-6        Write 16-bit instead of 8-bit
-4        Linear 16-bit, same as “-6 -W -g 1 1”
-T        Write TIFF instead of PPM

 

Podemos observar tres cosas:

  1. Podemos usar el modificador -i para chequear la utilidad, solo hay que usar una imagen y llamarla por ejemplo test.nef
  2. Debemos usar el modificador -T para pasar las imágenes a TIFF en lugar de PPM que es lo que hace por defecto
  3. Tenemos un problema, el resultado de la transformación es siempre el mismo fichero pero con la extensión cambiada, es decir, no podemos obligar a guardar el resultado en otro fichero (bueno, si, con el modificador -c que saca la salida por consola y capturandola con una redirección “>” pero esto no funciona bien)
Como Alfresco utiliza en la transformación dos variables ${source} y ${target} y ambos son distintos (genera los nombres de estos ficheros distintas) nos encontramos con el problema de que no podemos llamarlo directamente por lo que hay que crear un script que llame correctamente al ejecutable con los modificadores adecuados.

Este script en formato BAT/CMD de Windows y llamado dcraw.cmd es el siguiente:

@ECHO OFF

set path_dcraw=%0
SET opts=%1
SET source=%2
SET target=%3
%path_dcraw:~,-3%exe %opts% %source%
IF “%opts%”==”-i” GOTO END
MOVE %source:~,-4%tiff” %target%
:END

 

Seguidamente vamos a crear las variables en el fichero de propiedades, en este caso utilizo el mismo alfresco-global.properties:

dcraw.home=C:/Users/fernando.gonzalez/workspace/Common/MingW-release-orig-x32

dcraw.exe=${dcraw.home}/dcraw.cmd

 

Y por último debemos declarar los tipos MIME (mimetypes) en el fichero mimetypes-extension-map.xml que está en /alfresco/extension/mimetype:

<alfresco-config area="mimetype-map">
    <config evaluator="string-compare" condition="Mimetype Map">
        <mimetypes>
            <mimetype mimetype="image/x-nikon-nef" display="Nikon Raw Image">
                <extension>nef</extension>
            </mimetype>
            </mimetype>
        </mimetypes>
    </config>
</alfresco-config>
 Listo, ya solo nos falta hacer las reglas necesarias para convertir NEF a TIFF o a JPEG directamente en nuesto estudio fotográfico y tener almacenadas y ordenadas todas nuestras fotografías. Además, podemos usar Alfresco de previsualizador de imágenes RAW aunque no tengamos nigún programa para ello como podemos ver en la siguiente imagen:

Para depurar podemos activar las siguientes líneas en Log4J (log4j.properties o custom-log4j.propeties en el extension):

log4j.logger.org.alfresco.repo.content.transform.TransformerDebug=debug

log4j.logger.org.alfresco.util.exec.RuntimeExec=debug
log4j.logger.org.alfresco.repo.content.transform.ContentTransformerRegistry=debug

 

Cifrado de contenido en Alfresco

En muchas ocasiones es necesario el cifrado del contenido en Alfresco, en este sentido ya Alfresco en la versión 4.0 puede cifrar propiedades (http://wiki.alfresco.com/wiki/Data_Encryption) y también hay un módulo para utilizar el cifrado (http://addons.alfresco.com/addons/alfresco-encryption-module).

En este caso vamos a realizar dos acciones que cifren y descifren el contenido de un documento (propiedad content) usando el algoritmo de cifrado simétrico AES. Estos sirven para la versión 3.4 de Alfresco y siguientes.

El código también se encuentra en: http://code.google.com/p/alfcrypto

Algunas cosas importantes primero: Este software es una versión alpha o beta o como queráis llamarla pero sobre todo es un código hecho de forma más o menos rápida y por tanto no hay garantía ninguna de funcionamiento, se ha probado solo con algunos documentos MS-Word y PDF. Además, ya he detectado un problema, cuando se descifra el tipo MIME en el que se guarda la copia desencriptada es plain/text (no es que no funcione, si descargais el documento se puede abrir/editar, etc) por lo que hay que incluir en el modelo de datos una propiedad que guarde el valor original y lo restaure posteriormente (ya lo haré cuando consiga algo de tiempo). Se ha realizado solamente con carácter educativo y por lo tanto no lo recomiendo para su uso en sistemas de producción sin realizar antes algunas modificaciones y pruebas.

Otro apunte más, como bien comenta Toni de la Fuente (blyx.com), existen algunas restricciones derivadas del cifrado, la primera es en la previsualización, evidentemente no existe cuando los documentos están cifrados y no tiene sentido descifrar para previsualizar ya que rompería la seguridad. Tampoco es posible la indexación full-text ya que no es posible al igual que en contenidos de imágenes (JPG, GIF, etc.), es más, es una buena idea cambiar el tipo MIME a algún tipo que Alfresco no indexe y cuando sea descifrado reponer el tipo MIME original. El uso de este sistema sería para documentos que son necesariamente obligados a ser cifrados y solo se tenga acceso a ellos por personal especial (Recursos Humanos, I+D+I, datos con carácter especial de protección de datos, etc.) que una vez descifrados (en otra ubicación a la original principalmente) sean descargados y borrados (la copia descifrada) vaciando la papelera e incluso modificando la configuración para que no sean guardados en esta. Además recomendaría que o bien el cifrado, o el descifrado se aloje en otras unidades de disco diferentes usando para ello Content Store Selector (en este último caso solo para las versiones Enterprise).

Con todo esto, comencemos:

Lo primero que hay que hacer es construir la clase de cifrado que en este caso se llamará crypto.java:

/*
 * alfcrypto is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * alfcrypto is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see .
 */
package com.fegor.alfresco.security.crypto;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;

import org.apache.log4j.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

public class Crypto {
    private final Logger logger = Logger.getLogger(Crypto.class);

    String password = null;
    public final static int SALT_LEN = 8;
    byte[] vector_init = null;
    byte[] salt_pos = null;

    byte[] input;
    byte[] output;

    Cipher eCipher = null;
    Cipher deCipher = null;

    private final int KEYLEN_BITS = 128;
    private final int ITERATIONS = 65536;

    /**
     * Constructor
     */
    public Crypto() {
    }

    /**
     * Encryption configuration
     *
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidParameterSpecException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     * @throws InvalidKeyException
     */
    public void configEncrypt() throws NoSuchAlgorithmException,
            InvalidKeySpecException, NoSuchPaddingException,
            InvalidParameterSpecException, IllegalBlockSizeException,
            BadPaddingException, UnsupportedEncodingException,
            InvalidKeyException {
        SecretKeyFactory factory = null;
        SecretKey tmp = null;

        salt_pos = new byte[SALT_LEN];
        SecureRandom rnd = new SecureRandom();
        rnd.nextBytes(salt_pos);

        if (logger.isDebugEnabled())
            logger.debug(this.getClass().getName() + “: [salt: “
                    + (new String(Hex.encodeHex(salt_pos))) + “]”);

        factory = SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”);

        /*
         * http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files
         * .shtml
         */
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt_pos,
                ITERATIONS, KEYLEN_BITS);
        tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), “AES”);

        eCipher = Cipher.getInstance(“AES/CBC/PKCS5Padding”);
        eCipher.init(Cipher.ENCRYPT_MODE, secret);
        AlgorithmParameters params = eCipher.getParameters();

        vector_init = params.getParameterSpec(IvParameterSpec.class).getIV();

        if (logger.isDebugEnabled())
            logger.debug(this.getClass().getName() + “: [vector ini: “
                    + (new String(Hex.encodeHex(vector_init))) + “]”);
    }

    /**
     * Decryption configuration
     *
     * @param initvec
     * @param salt
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     * @throws DecoderException
     */
    public void configDecrypt(String initvec, String salt)
            throws NoSuchAlgorithmException, InvalidKeySpecException,
            NoSuchPaddingException, InvalidKeyException,
            InvalidAlgorithmParameterException, DecoderException {
        SecretKeyFactory factory = null;
        SecretKey tmp = null;
        SecretKey secret = null;

        salt_pos = Hex.decodeHex(salt.toCharArray());

        if (logger.isDebugEnabled())
            logger.debug(this.getClass().getName() + “: [salt: “
                    + (new String(Hex.encodeHex(salt_pos))) + “]”);

        vector_init = Hex.decodeHex(initvec.toCharArray());
        if (logger.isDebugEnabled())
            logger.debug(this.getClass().getName() + “: [vector ini: “
                    + (new String(Hex.encodeHex(vector_init))) + “]”);

        /*
         * http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files
         * .shtml
         */
        factory = SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”);
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt_pos,
                ITERATIONS, KEYLEN_BITS);

        tmp = factory.generateSecret(spec);
        secret = new SecretKeySpec(tmp.getEncoded(), “AES”);

        deCipher = Cipher.getInstance(“AES/CBC/PKCS5Padding”);
        deCipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(
                vector_init));
    }

    /**
     * Cipher input
     *
     * @param input
     *            – the cleartext file to be encrypted
     * @param output
     *            – the encrypted data file
     * @throws IOException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws ShortBufferException
     */
    public void Cipher() throws IOException, IllegalBlockSizeException,
            BadPaddingException, ShortBufferException {
        try {
            this.output = eCipher.doFinal(this.input);
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
    }

    /**
     * Decipher input
     *
     * @param input
     *            – the cleartext file to be encrypted
     * @param output
     *            – the encrypted data file
     * @throws IOException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws ShortBufferException
     */
    public void Decipher() throws IOException, IllegalBlockSizeException,
            BadPaddingException, ShortBufferException {
        try {
            this.output = deCipher.doFinal(this.input);
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
    }

    /*
     * Methods setter and getter
     */
    public void setInput(byte[] input) {
        this.input = input;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSalt() {
        return (new String(Hex.encodeHex(salt_pos)));
    }

    public String getVectorInit() {
        return (new String(Hex.encodeHex(vector_init)));
    }

    public byte[] getOutput() {
        return this.output;
    }
}

Como se observa es una clase normal con los métodos para configurar el cifrado y descifrado y la llamada para realizar las acciones correspondientes.

Utilizaremos dos aspectos para saber que documentos están cifrados y cuales han sido descifrados, el modelo de datos a utilizar será el siguiente:

   
    Alfresco Crypto Model
    Fernando González Ruano (twitter://fegorama)
    1.0
   
        <import uri="http://www.alfresco.org/model/dictionary/1.0"
            prefix=”d” />
       
   
   
        <namespace uri="http://www.fegorsoft.com/model/alfcrypto/1.0"
            prefix=”acr” />
   
   
       
            Ciphered
           
               
                    d:text
                    false
                   
                        false
                        false
                        false
                   
               
               
                    d:text
                    false
                   
                        false
                        false
                        false
                   
                               
           
       
       
            Deciphered
       
   

Para llamar a esta clase se necesitan dos acciones de Alfresco, estas serán CipherContent.java y DecipherContent.java:

Fichero: CipherContent.java
/*
 * alfcrypto is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * alfcrypto is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see .
 */
package com.fegor.alfresco.action;

import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.util.HashMap;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;

import com.fegor.alfresco.model.AlfCryptoModel;
import com.fegor.alfresco.security.crypto.Crypto;
import com.google.gdata.util.common.util.Base64;

/**
 * CryptoRepo Action
 *
 * @author fegor
 *
 */
public class CipherContent extends ActionExecuterAbstractBase {

    private final Logger logger = Logger.getLogger(CipherContent.class);

    /*
     * Services
     */
    private ContentService contentService;
    private NodeService nodeService;

    private String password;
    //
    // TODO Poder usar más algoritmos que AES
    //
    // private String algorithm;

    private String salt;
    private String vector_init;

    /*
     * (non-Javadoc)
     *
     * @see
     * org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl
     * (org.alfresco.service.cmr.action.Action,
     * org.alfresco.service.cmr.repository.NodeRef)
     */
    @Override
    protected void executeImpl(Action action, NodeRef actionedUponNodeRef) {
        if (!nodeService.hasAspect(actionedUponNodeRef,
                AlfCryptoModel.ASPECT_CIPHERED)) {
            if (logger.isDebugEnabled()) {
                logger.debug(this.getClass().getName() + “: [Action for: “
                        + actionedUponNodeRef + ” is ciphering…]”);
            }
            if (actionedUponNodeRef != null)
                try {
                    this.cryptoFileCipher(actionedUponNodeRef);
                } catch (ContentIOException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#
     * addParameterDefinitions(java.util.List)
     */
    @Override
    protected void addParameterDefinitions(List arg0) {
    }

    /**
     * Crypto file for nodeRef
     *
     * @param nodeRef
     * @throws IOException
     * @throws ContentIOException
     */
    private void cryptoFileCipher(NodeRef nodeRef) throws ContentIOException,
            IOException {
        ContentReader contentReader = this.contentService.getReader(nodeRef,
                ContentModel.PROP_CONTENT);
        ContentWriter contentWriter = this.contentService.getWriter(nodeRef,
                ContentModel.PROP_CONTENT, true);

        if (contentReader != null) {
            Crypto crypto = new Crypto();
            crypto.setPassword(this.password);
            byte[] crb = IOUtils.toByteArray(contentReader
                    .getContentInputStream());

            try {
                crypto.configEncrypt();
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeySpecException e) {
                e.printStackTrace();
            } catch (NoSuchPaddingException e) {
                e.printStackTrace();
            } catch (InvalidParameterSpecException e) {
                e.printStackTrace();
            } catch (IllegalBlockSizeException e) {
                e.printStackTrace();
            } catch (BadPaddingException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            crypto.setInput(crb);
            try {
                crypto.Cipher();
            } catch (IllegalBlockSizeException e) {
                e.printStackTrace();
            } catch (BadPaddingException e) {
                e.printStackTrace();
            } catch (ShortBufferException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            contentWriter.putContent(Base64.encode(crypto.getOutput()));

            this.salt = crypto.getSalt();
            this.vector_init = crypto.getVectorInit();

            this.removeAspect(nodeRef);
            this.addAspect(nodeRef);

        } else {
            if (logger.isDebugEnabled())
                logger.debug(this.getClass().getName()
                        + “: [contentReader is null]”);
        }
    }

    /**
     * Remove aspect Deciphered
     *
     * @param nodeRef
     */
    private void removeAspect(NodeRef nodeRef) {
        if (nodeService.hasAspect(nodeRef, AlfCryptoModel.ASPECT_DECIPHERED)) {
            nodeService.removeAspect(nodeRef, AlfCryptoModel.ASPECT_DECIPHERED);
        }
    }

    /**
     * Add aspect Ciphered
     *
     * @param nodeRef
     */
    private void addAspect(NodeRef nodeRef) {
        HashMap properties = new HashMap(
                1, 1.0f);
        properties.put(AlfCryptoModel.PROP_SALT, this.salt);
        properties.put(AlfCryptoModel.PROP_VECTOR_INIT, this.vector_init);
        if (!nodeService.hasAspect(nodeRef, AlfCryptoModel.ASPECT_CIPHERED)) {
            nodeService.addAspect(nodeRef, AlfCryptoModel.ASPECT_CIPHERED,
                    properties);
        }
    }

    /**
     * @param contentService
     */
    public void setContentService(ContentService contentService) {
        this.contentService = contentService;
    }

    /**
     * @param nodeService
     */
    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    /**
     * @param password
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * @param algorithm
     */
    //
    // TODO Poder usar más algoritmos que AES
    //
    // public void setAlgorithm(String algorithm) {
    // this.algorithm = algorithm;
    // }
}

Fichero: DecipherContent.java
/*
 * alfcrypto is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * alfcrypto is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see .
 */
package com.fegor.alfresco.action;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.apache.commons.codec.DecoderException;
import org.apache.log4j.Logger;

import com.fegor.alfresco.model.AlfCryptoModel;
import com.fegor.alfresco.security.crypto.Crypto;
import com.google.gdata.util.common.util.Base64;
import com.google.gdata.util.common.util.Base64DecoderException;

/**
 * DecryptoRepo Action
 *
 * @author fegor
 *
 */
public class DecipherContent extends ActionExecuterAbstractBase {

    private final Logger logger = Logger.getLogger(DecipherContent.class);

    /*
     * Services
     */
    private ContentService contentService;
    private NodeService nodeService;

    private String password;
    //
    // TODO Poder usar más algoritmos que AES
    //
    // private String algorithm;

    private String salt;
    private String vector_init;

    /*
     * (non-Javadoc)
     *
     * @see
     * org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl
     * (org.alfresco.service.cmr.action.Action,
     * org.alfresco.service.cmr.repository.NodeRef)
     */
    @Override
    protected void executeImpl(Action action, NodeRef actionedUponNodeRef) {
        if (nodeService.hasAspect(actionedUponNodeRef,
                AlfCryptoModel.ASPECT_CIPHERED)) {
            if (logger.isDebugEnabled()) {
                logger.debug(this.getClass().getName() + “: [Action for: “
                        + actionedUponNodeRef + ” is deciphering…]”);
            }
            if (actionedUponNodeRef != null)
                try {
                    this.cryptoFileDecipher(actionedUponNodeRef);
                } catch (InvalidAlgorithmParameterException e) {
                    e.printStackTrace();
                } catch (DecoderException e) {
                    e.printStackTrace();
                }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#
     * addParameterDefinitions(java.util.List)
     */
    @Override
    protected void addParameterDefinitions(List arg0) {
    }

    /**
     * Scan file for nodeRef
     *
     * @param nodeRef
     * @throws DecoderException
     * @throws InvalidAlgorithmParameterException
     */
    private void cryptoFileDecipher(NodeRef nodeRef)
            throws InvalidAlgorithmParameterException, DecoderException {
        ContentReader contentReader = this.contentService.getReader(nodeRef,
                ContentModel.PROP_CONTENT);
        ContentWriter contentWriter = this.contentService.getWriter(nodeRef,
                ContentModel.PROP_CONTENT, true);

        if (contentReader != null) {

            byte[] crb = contentReader.getContentString().getBytes();
            Crypto crypto = new Crypto();
            crypto.setPassword(this.password);
            this.salt = (String) nodeService.getProperty(nodeRef,
                    AlfCryptoModel.PROP_SALT);
            this.vector_init = (String) nodeService.getProperty(nodeRef,
                    AlfCryptoModel.PROP_VECTOR_INIT);

            try {
                crypto.configDecrypt(this.vector_init, this.salt);
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeySpecException e) {
                e.printStackTrace();
            } catch (NoSuchPaddingException e) {
                e.printStackTrace();
            }

            try {
                crypto.setInput(Base64.decode(crb));
            } catch (Base64DecoderException e1) {
                e1.printStackTrace();
            }

            try {
                crypto.Decipher();
            } catch (IllegalBlockSizeException e) {
                e.printStackTrace();
            } catch (BadPaddingException e) {
                e.printStackTrace();
            } catch (ShortBufferException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            contentWriter.setMimetype(“text/plain”);
            contentWriter.putContent((InputStream) (new ByteArrayInputStream(
                    crypto.getOutput())));

            this.removeAspect(nodeRef);
            this.addAspect(nodeRef);

        } else {
            if (logger.isDebugEnabled())
                logger.debug(this.getClass().getName()
                        + “: [contentReader is null]”);
        }
    }

    /**
     * Remove aspect Ciphered
     *
     * @param nodeRef
     */
    private void removeAspect(NodeRef nodeRef) {
        if (nodeService.hasAspect(nodeRef, AlfCryptoModel.ASPECT_CIPHERED)) {
            nodeService.removeAspect(nodeRef, AlfCryptoModel.ASPECT_CIPHERED);
        }
    }

    /**
     * Add aspect Deciphered
     *
     * @param nodeRef
     */
    private void addAspect(NodeRef nodeRef) {
        if (!nodeService.hasAspect(nodeRef, AlfCryptoModel.ASPECT_CIPHERED)) {
            nodeService
                    .addAspect(nodeRef, AlfCryptoModel.ASPECT_CIPHERED, null);
        }
    }

    /**
     * @param contentService
     */
    public void setContentService(ContentService contentService) {
        this.contentService = contentService;
    }

    /**
     * @param nodeService
     */
    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    /**
     * @param password
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * @param algorithm
     */
    //
    // TODO Poder usar más algoritmos que AES
    //
    // public void setAlgorithm(String algorithm) {
    // this.algorithm = algorithm;
    // }
}

El siguiente paso es configurar y registrar las acciones en Alfresco:

Fichero: actions-context.xml

    
       
            true
       
       
           
                classpath:alfresco/extension/alfcrypto.properties
           
       
   
   
    <bean id="alfcrypto.cipher.action" class="com.fegor.alfresco.action.CipherContent"
        parent=”action-executer”>
       
           
       
       
           
       
       
        <!–
       
            ${alfviral.algorithm}
       
         –>
       
            ${alfviral.password}
       
   

    <bean id="alfcrypto.decipher.action" class="com.fegor.alfresco.action.DecipherContent"
        parent=”action-executer”>
       
           
       
       
           
       
       
        <!–
       
            ${alfviral.algorithm}
       
         –>
       
            ${alfviral.password}
       
       

Fichero: model-context.xml

       
   
       
           
                alfresco/module/alfcrypto/model/alfcryptoModel.xml
           
       
     


Fichero: webclient-context.xml

    
       
           
                alfresco.module.alfcrypto.messages.alfcrypto
           
       
    
  
    <bean id="alfcrypto.webclient.configBootstrap" class="org.alfresco.web.config.WebClientConfigBootstrap"
        init-method=”init”>
       
           
                classpath:alfresco/module/alfcrypto/ui/web-client-config-custom.xml
           
       
    

Fichero: web-client-config-custom.xml

  
     
        
        
     
  
  
  
   
  
  
  
     
        
        
        
  
  

Fichero: alfcrypto.properties (messages)
alfcrypto.cipher.action.title=Cifrar
alfcrypto.cipher.action.description=Cifrado del contenido

alfcrypto.decipher.action.title=Descifrar
alfcrypto.decipher.action.description=Descifrado del contenido

alfcrypto.label.ciphered=Cifrado
alfcrypto.label.deciphered=Descifrado

Y por último construir el fichero de configuración:

Fichero: alfcrypto.properties
# La elección de algoritmo (alfcrypto.algorithm) no está implementado todavía
alfcrypto.algorithm=AES
alfcrypto.password=estoesunaclavesecreta

A partir de aquí podemos utilizar el sistema para cifrar y descifrar, para ello podemos usar reglas para que el contenido en una carpeta sea cifrado, crear un workflow para descifrar el contenido de los documentos cifrados, etc.

Solo cifra el contenido, no las propiedades de los documentos.

Más sobre este tema:
http://es.wikipedia.org/wiki/Advanced_Encryption_Standard
http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
http://wiki.alfresco.com/wiki/Data_Encryption
http://addons.alfresco.com/addons/alfresco-encryption-module

Actualización de Alfviral 1.2.1

Debido a un “desliz” en mi código con la versión 1.2.0 se me olvidó declarar el servicio NodeService para usarlo en los modos COMMAND y INSTREAM por lo que he arreglado esto y algún que otro retoque más y he subido la versión 1.2.1 que os recomiendo que instaléis en lugar de la versión anterior.

Agradezco a Niccolò Pasquetto su aportación y aviso.

La nueva versión se puede descargar directamente desde:

https://alfviral.googlecode.com/files/fegorsoft-alfviral-1.2.1.zip

Como no sobrecargar form.get.head.ftl en Alfresco

En el blog de Michal Wróbel y su magnífico artículo “How to perform form field validation in Alfresco Share?” se explica la forma de usar los eventos para validaciones de campos en formulario de Alfresco Share. Lo hace sobreescribiendo la restricción obligatoria (mandatory constraint).

El problema viene cuando creamos un módulo para Alfresco Share con su fichero JAR correspondiente y lo instalamos en un Alfresco Share donde hay otro fichero form.get.head.ftl ya que el del JAR anulará al anterior con lo que no funcionarán las validaciones.

Para esto he decidido usar otra forma de tener cargado el fichero .js que necesitamos para las validaciones, por ejemplo form_validation_module.js dentro de nuestra instalación. La forma es usando la referencia a ficheros javascript de la configuración de los “forms” de forma que podamos tener y cargar los ficheros por cada módulo que tengamos sin tener que tocar el fichero original.

De esta forma, podremos tener el mismo fichero form_validation_module.js en la misma ubicación tomcat/webapps/share/components/form pero ahora lo referenciamos no en form.get.head.ftl sino dentro de nuestro fichero de configuración de los campos, por ejemplo module-form-config.xml de esta forma:


   
       
           
               
           

       
   


   
       
           
               
                   
               
               
                   
                       
                            <constraint type="mandatory"
                                validation-handler=”Alfresco.forms.validation.prueba”/>
                       
                   
               
           
       
   

Lo único que cambia del anterior método es que aquí no hace falta referenciar la parte ${page.url.context}/res y que hay que añadir “-min” al fichero si lo vamos a “compactar”.

De esta forma tendrémos más seguridad a la hora de desplegar distintos módulos para Alfresco Share donde haya otros ya instalados.

Alfviral versión 1.1.0

Por fin el 21 de agosto liberé la versión 1.1.0 del módulo para escaneo de documentos mediante antivirus llamado Alfviral (el nombre a lo mejor no es muy apropiado pero fue el que se me ocurrió en ese momento).

Básicamente lo que se ha incluido ha sido una pequeña refactorización del código y la inclusión de la posibilidad de poder escanear los documentos mediante la página www.virustotal.com que a su vez utiliza más de 40 motores de antivirus.

Las características actuales del módulo son:

1. Escanear en base a tres modos distintos:

COMMAND: Permite utilizar un script o directamente el programa ejecutable del antivirus que se quiera siempre y cuando permita al menos un parámetro que sea el fichero a escanear. Este proceso depende del arranque del comando antivirus en si.

INSTREAM: Utilizado para lanzar un flujo de datos del fichero en Alfresco hacia el puerto (3310TCP) utilizado por ClamAV. Esto permite tener un antivirus ClamAV central en un servidor y utilizarlo remotamente desde Alfresco.

VIRUSTOTAL: En este modo se sube el fichero que sube o se actualiza en Alfresco a la web virustotal.com vía HTTP mediante el método POST y se recoge el resultado mediante JSON para su análisis.

2. Si el documento se detecta como infectado se añade el aspecto “infected” así como un aspecto adicional dependiendo del método utilizado para su análisis.

3. Uso de las “policies” de Alfresco usando los métodos onContentUpdate y onContentRead para analizar los documentos que son subidos, modificados y/o leídos.

4. Se puede utilizar el análisis mediante una programación de tiempo indicando a partir de qué espacio se quiere analizar y si se utiliza en profundidad, es decir, a los subespacios.

5. Implemena acciones de usuario tanto en la interface /alfresco como en /share para poder analizar documentos de forma interactiva.

6. Uso de reglas para personalizar los análisis utilizando la acción de escanear.

7. Facilidad de instalación utilizando las Alfresco Module Management Tools para la parte de repositorio y copiando directamente una librería en extension/lib para Share.

8. Configuración flexible y sencilla para establecer el modo de análisis y las formas en las que se quiere realizar el análisis:

– Al subir/modificar un documento
– Al leer un documento
– En una programación horaria y/o de fecha concreta
– Desde una carpeta (espacio de trabajo) en adelante

Un ejemplo de configuración es:

# Command to exec, i.e. clamscan, alfviral.sh, etc.
alfviral.command=C:\Users\fegor\Documents\alfviral.bat
# Config for ClamAV in stream data
alfviral.timeout=30000
alfviral.host=127.0.0.1
alfviral.port=3310
#Config for VIRUSTOTAL
vt.key=246df658bca5e096895683c01ba4bd2eb3a00303b506bda774b71488134bf984
vt.url=https://www.virustotal.com/vtapi/v2/file/scan
# Modes: COMMAND, INSTREAM, VIRUSTOTAL
alfviral.mode=VIRUSTOTAL
# Events
alfviral.on_update=TRUE
alfviral.on_read=FALSE
# Scheduled action
alfviral.scheduled.pathQuery=/app:company_home/st:sites
alfviral.scheduled.cronExpression=* * 3 * * ? 2099

Toda la información tanto en español como en inglés está en la página principal del proyecto: http://code.google.com/p/alfviral/

Como es un proyecto libre y personal las mejoras y nuevas funcionalidades dependen mucho de mi tiempo libre pero más o menos algunas ideas que tengo en mente son:

– Añadir estadísticas sobre los documentos infectados
– Posibilidad de mover los documentos infectados a un espacio de cuarentena
– Recuperación de los documentos infectados a su ubicación original si son desinfectados o se eliminan los aspectos de infección.
– Añadir nuevos protocolos de comunicación con antivirus (Symantec, McAfee,…)
– Poder utilizar distintos modos de análisis al mismo tiempo y para diferentes objetos (espacios y ficheros)

Problemas en la previsualización de documentos PDF en Alfresco

Primer problema:

En ocasiones los ficheros PDF no pueden ser visualizados por el Share de Alfresco. Esto puede ser debido a que si el PDF tiene activas las restricciones de impresión y/o copia, la librería SWFTools y en concreto la utilidad pdf2swf no es capaz de transformar el fichero.

Para estar seguros de que es este el problema, podemos llamar directamente a la utilidad con el fichero PDF y comprobar el resultado:

pdf2swf prueba.pdf -o prueba.swf

Si el mensaje que devuelve es “FATAL   PDF disallows copying” entonces estamos ante este problema.

La forma de solucionarlo es recompilar SWFTools y de esta forma evitamos el aviso y podremos ver los documentos.

Las siguientes instrucciones son para la compilación bajo Linux:

Bajamos el paquete de código fuente de las SWFTools (p.e. la versión 0.9.2 está probada en la versión 3.4.8 de Alfresco funcionando perfectamente), lo descomprimimos y entramos en el directorio creado.

En lib/pdf/pdf.cc comentamos las dos líneas siguientes:

if(!pi->config_print && pi->nocopy) {msg(” PDF disallows copying”);exit(0);}
if(pi->config_print && pi->noprint) {msg(” PDF disallows printing”);exit(0);}}

Configuramos de forma estática para que podamos llevarlo a otras distribuciones de Linux (yo la he probado en Ubuntu 11.10):

CC=/usr/bin/gcc-4.6 CXX=/usr/bin/g++-4.6 LDFLAGS=”-static” ./configure

Seguramente tengamos que instalar los paquetes para desarrollo como es el g++ y el gcc.

Luego:

make

Y por último:

make install 

O bien, simplemente sustituimos la utilidad pdf2swf que es la que se utiliza. Ya podremos ver los PDF bloqueados normalmente.

Segundo problema:

Otro problema que encontramos es que en la versión 3.4.x y 4.x los PDF se ven borrosos en la previsualización, esto es debido a los modificadores establecidos para realizar la conversión a SWF que pone Alfresco.

Para solucionarla tendremos que modificar el fichero webapps/alfresco/WEB-INF/classes/alfresco/subsystems/thirdparty/default/swf-transform.properties que generalmente tiene la siguiente línea:

swf.encoder.params=-s zoom=72 -s ppmsubpixels=1 -s poly2bitmap=1 -s bitmapfonts=1

Por esta otra (por ejemplo):

swf.encoder.params=-s zoom=100 -s ppmsubpixels=1 -s poly2bitmap=1 -s bitmapfonts=1

De esta forma se verán correctamente, eso si, el tiempo de conversión será mayor.

ACTUALIZACIÓN (12/07/2012): Aunque para compilar SWFTools se puede usar la información de los enlaces que doy al final, para Ubuntu 11 (y probado en la 12) tanto para amd64 como i386 se pueden seguir la siguiente secuencia de comandos de forma rápida:

sudo apt-get install build-essential checkinstall
sudo chown $USER /usr/local/src
sudo chmod u+rwx /usr/local/src
sudo apt-get install libgif-dev xpdf libfreetype6 libfreetype6-dev libjpeg62 libjpeg8 libjpeg8-dev
sudo wget http://www.swftools.org/swftools-0.9.1.tar.gz
cd lib/pdf
sudo wget http://gd.tuwien.ac.at/publishing/xpdf/xpdf-3.02.tar.gz
./configure
make


sudo checkinstall

Fuentes:
http://monkiki.wordpress.com/2010/04/19/compilar-pdf2swf-con-soporte-para-pdf-protegidos/
http://loftux.com/2012/01/08/replace-alfresco-standard-flash-viewer-with-pdf-js/
http://www.vservu.com/_blog/MegaZine3_-_tips,_tricks_and_hints/post/pdf2swf-switches/
http://www.swftools.org/
https://designbye.wordpress.com/2010/02/23/installing-swftools-and-pdf2swf-on-ubuntu-linux/
http://ubuntuforums.org/showthread.php?t=1821521

Enviar documentos a Alfresco de forma desantendida

En ocasiones necesitamos enviar a Alfresco documentos que han sido escaneados o generados por otras aplicaciones de forma automatizada.

La forma más común de “inyectar” documentos a Alfresco es mediante el uso del protocolo CIFS, de forma que podemos usar una unidad de red o recurso compartido (según el sistema operativo utilizado) para guardar el documento directamente en el entorno de Alfresco.

En ocasiones, bien porque el producto está limitado a usar unidades de red, o bien, porque necesitamos que los ficheros generados se envíen de alguna forma u orden y a una hora o espacio de tiempo determinado, hay que usar sistemas de copia o de movimiento de ficheros de forma desatendida.

En este caso, se ha utilizado un sistema utilizando el producto DigiDocFlow (http://www.digidocflow.com/) que en lugar de guardar el documento escaneado y el correspondiente fichero XML con los valores de los metadatos en el recurso de red del servidor donde está instalado Alfresco, se ha utilizado un directorio local de la máquina donde está instalado el producto (en Windows XP) y mediante un proceso batch se copian cada minuto a una unidad compartida de red que hace referencia a una carpeta de Alfresco llamada FROMSCAN.

Los pasos han sido:

1. Crear una carpeta en Company Home de Alfresco llamada FROMSCAN. Esta llevará las reglas necesarias para mover los documentos que entran.

2. Crear una carpeta local en Windows XP donde está instalado DigiDocFlow llamada también FROMSCAN.

3. Escribir el siguiente fichero batch:

@ECHO OFF
SET CARPETAWIN=C:FROMSCAN
SET CARPETAALF=Y:FROMSCAN
COPY /Y “%CARPETAWIN%*.pdf” “%CARPETAALF%”
COPY /Y “%CARPETAWIN%*.xml” “%CARPETAALF%”
IF ERRORLEVEL 1 GOTO SALIR
DEL /F /Q “%CARPETAWIN%*.*”
:SALIR

4. Crear la tarea (ejecutar en intérprete de comandos en Windows XP llamado CMD.EXE) para que se realice (ejecute) el batch cada hora:

SCHTASKS /CREATE /TN FROMSCAN /TR “CMD /C “C:FROMSCAN.BAT”” /SC HOURLY

Hay que tener en cuenta “escapar” las comillas con para que no se produzcan errores, no en este caso, pero si, si en el directorio o nombre de fichero batch se contienen caracteres en blanco.

De esta forma podemos tener incluso un directorio local donde van a parar ficheros que provienen de un ERP, un CRM o un sistema de escaneo como Kofax o DigiDocFlow sin tener que usar ningún tipo de conector y personalizando tanto el tiempo de envío de los ficheros como el orden.

Instalar WQS y AWE en WebSphere 7

Pasos para incluir en el fichero .ear de Alfresco 3.4.x Enterprise el módulo Web Quick Start y Alfresco Web Editor para su instalación en WebSphere Application Server.

  1. Bajar Web Quick Start (zip bundle)

alfresco-enterprise-wcmqs-3.4.4.zip

  2. Desempaquetar el fichero EAR

mkdir alfresco-enterprise-3.4.3.3-WQS
cd alfresco-enterprise-3.4.3.3-WQS
jar -xvf ../alfresco-enterprise-3.4.3.3.ear

  3. Desempaquetar el fichero que contiene WQS

jar -xvf ../alfresco-enterprise-wcmqs-3.4.3.zip

  4. Desempaquetar el fichero que contiene el Alfresco Web Editor

jar -xvf ../alfresco-enterprise-webeditor-3.4.3.zip

  5. Instalar paquetes AMP en Alfresco y Share

java -jar ../alfresco-mmt-3.3.5.jar install alfresco-enterprise-wcmqs-3.4.3.amp alfresco.war -verbose
java -jar ../alfresco-mmt-3.3.5.jar install alfresco-enterprise-wcmqs-share-3.4.3.amp share.war -verbose

  6. Editar application.xml de META-INF y añadir los nuevos WAR

   Alfresco
  
    
         alfresco.war
         /alfresco
    
  
  
    
         share.war
         /share
    
  
  
    
         wcmqs.war
         /wcmqs
    
  
  
    
         awe.war
         /awe
    
  
  
    
         customer.war
         /customer
    
  

  7. Empaquetar como EAR todos los ficheros WAR

jar cvf alfresco-enterprise-3.4.3.3-WQS.ear *.war

  8. Mover los ficheros jar a una ubicación para compartirla en WAS

mkdir -p /opt/IBM/WebSphere/AppServer/lib/awe
mv *.jar /opt/IBM/WebSphere/AppServer/lib/awe

  9. Mover los ficheros xml al “extension” de configuración y añadir lo siguiente para la conexión.
mv awe-config-custom.xml /opt/alf343WAS7_shared_cluster/web-extension
vi /opt/alf343WAS7_shared_cluster/web-extension/awe-config-custom.xml
(incluir lo siguiente):

  
     
        
     
  

  
     
        
            alfresco
            Alfresco – user access
            Access to Alfresco Repository WebScripts that require user authentication
            alfresco
            http://localhost:9080/alfresco/s
           
            user
        
     
  

  10. En WebSphere, crear una biblioteca compartida para los ficheros y asignarla a la aplicación AWE.

  11. Actualizar aplicación desde el Manager de WAS 7 usando el fichero ear creado e iniciar Alfresco ECM.

  12. Copiar el fichero de propiedades de wcmqs al extension y modificarlo si es necesario.
cp ../installedApps/alfpru1Cell01/Alfresco.ear/wcmqs.war/WEB-INF/classes/alfresco/extension/wqsapi-custom.properties /opt/alf343WAS7_shared_cluster/extension/

vi /opt/alf343WAS7_shared_cluster/extension/wqsapi-custom.properties

(contenido del fichero)
wcmqs.api.alfresco=http://localhost:9080/alfresco
wcmqs.api.user=admin
wcmqs.api.password=admin

Datos sobre la instalación:
Alfresco 3.4.3 en clúster sobre WebSphere 7
Directorio de instalación: /opt/IBM/WebSphere
Directorio de configuración (vía NFS): /opt/alf343WAS7_share_cluster
Directorio del repositorio (vía NFS): /opt/alf343WAS7_repo_cluster
Directorio de índices: /opt/alf343WAS7_index_cluster

Para más información, ir a la documentación oficial de Alfresco (http://docs.alfresco.com) y a la wiki (http://wiki.alfresco.com).

Quiero agradecer la ayuda y consejos de Mikel Asla, experto en sistemas, Alfresco y WebSphere.

Buenas prácticas en Alfresco ECM

Verión: 1 – Revisión: 0 – Publicación: 4/10/2011 – Última modificación: 4-oct-2011

Introducción
La instalación de Alfresco ECM requiere de una requisitos previos así como unas acciones posteriores para que el sistema comience a funcionar correctamente y se mantenga “sano” surante todo el tiempo que esté gestionando la documentación y registros. Este artículo pretende ser solamente un compendio de consejos y buenas prácticas y por tanto se irá modificando y ampliando en la medida de lo posible.

El ciclo de instalación, configuración y mantenimiento de Alfresco ECM comprende las siguientes fases:

Fase de preparación
1.    Diseñar tanto la arquitectura física como la arquitectura lógica antes de comenzar la instalación, si bien pueden usarse diagramas mixtos, es preferible realizar estos diseños por separado lo que nos dará también la posibilidad de generar lo sentregables para los distintos departamentos (comunicaciones y hardware, sistemas, etc.)
2.    Utilizar arquitecturas de 64 bits preferentemente, tanto a nivel de Hardware como Software (Sistema Operativo, Máquina Virtual de Java, etc.). Esto es muy importante en la medida en que en sistemas de 32 bits. se limita a nivel de direccionamiento de memoria principalemente.
3.    Usar procesadores de doble núcleo como mínimo y procesadores de 2,5GHz. en adelante.
4.    Usar la matriz de compatibilidad establecida por Alfresco para la instalación de todos los componentes: http://www.alfresco.com/services/subscription/supported-platforms/
5.    Adecuar la instalación a la arquitectura planteada y verificar la disponibilidad de recursos (NAS/SAN, SGDB,…) y que estos están disponibles, que son montados en el inicio de la máquina o al menos antes de arranque de Alfresco ECM.

Ejemplos (en Linux/Unix/MacOS X):
   Comprobar las unidades montadas para verificar su existencia: mount
   Verificar la correcta escritura: touch prueba

Comprobar que el servidor de la SGDB está funcionando: ping servidorsgdb
6.    Verificar la disponibilidad de los puertos que son necesarios para la instalación. Algunos puertos importantes para Alfresco son los siguientes:

  • a.    FTP: TCP 21 (se recomienda desconectar)
  • b.    SMTP: TCP 25
  • c.    SMB / NetBT: UDP 137,138, TCP 139,445 (para determinados entornos no es aconsejable)
  • d.    IMAP: TCP 143
  • e.    SharePoint Protocol: TCP 7070
  • f.    Tomcat Administration: TCP 8005
  • g.    HTTP: TCP 8080 (Tomcat, JBoss,…) / 9080 (para WebSphere) /…
  • h.    RMI: TCP 50500

Ejemplos (en Linux/Unix/MacOS X):
   Comprobar la existencia del puerto SMTP: telnet servidoralfresco 25
   Otra forma de comprobar que el puerto está abierto: nmap -P0 -p T:21,25,110,8080 servidoralfresco
   Puertos abiertos en el mismo servidor: netstat -putan

7.    Verificar la correcta comunicación tanto a nivel de fiabilidad como de estabilidad, verificar la latencia y rapidez de las transferencias:

  • a.    Conexión con el SGBD.
  • b.    I/O del disco que almacena los índices de Lucene.
  • c.    I/O del disco que almacena el repositorio.
  • d.    Conectividad entre los nodos (clúster).
  • e.    Conectividad con servidor NTP.

 Ejemplos (en Linux/Unix/MacOS X):
   Comprobar la transferencias a discos locales cada 2 segundos: iostat -w 2
   Visualizar las estadísticas de red cada 2 segundos: netstat -s -p tcp -w 2

8.    Comprobar la configuración con el SGDB con un DBA certificado así como la configuración del sistema de almacenamiento del repositorio con un experto certificado en el sistema de archivo usado (GFS, OCFS, VxFS, etc.). Es muy aconsejable que tanto la SGBD como el lugar donde se aloja el repositorio se conecten mediante “fibre channel” para evitar excesivas latencias y lentitud en las transacciones.
9.    Usar Alfresco Environment Validation Tool (Alfresco EVT) para validar el entorno (http://code.google.com/p/alfresco-environment-validation/)

Fase de instalación
1.    Crear una plantilla estándar adecuada al tipo de instalación y entorno con “checks” de control de las tareas. Es importante que los técnicos que instalen los sistemas, rellenen correctamente estas hojas e indiquen todas las incidencias que encuentran.

2.    Verificar los parámetros para la instalación, espacio disponible en discos, memoria RAM de los equipos y memoria Heap/Stack/Perm a usar para la JVM según el fabricante soportado (SUN, ORACLE, etc.), descriptores posibles, máximas conexiones del servidor de aplicaciones así como de firewalls y proxies puestos delante de Alfresco ECM.

Recomendaciones básicas de memoria para producción:
– Heap (-Xmx): 4G
– Pila (-Xss): 256k
– Perm (-XX:MaxPermSize): 256m

Ejemplos (en Linux/Unix/MacOS X):
   Kernel: uname -a
   Datos del sistema: cat /proc/cpuinfo
   Verificar memoria: free
   Procesos java arrancados: jps -v
   Procesos java arrancados: ps -fea | grep java

3.    Utilizar las recomendaciones de Alfresco para la ubicación de los archivos:

  • a.    ${TOMCAT_HOME}/shared/classes/extension/alfresco
  • b.    ${WEBSPHERE_HOME}/lib/alfresco
  • c.    ${JBOSS_HOME}/conf/alfresco

4.    NO incluir nunca ficheros de configuración en el despliegue de Alfresco ECM excepto por parte de módulos.
5.    Usar SIEMPRE módulos (mmt) para la instalación de nuevas funcionalidades, personalizaciones y configuraciones de Alfresco ECM.
6.    Usar las recomendaciones de Alfresco para la creación de ficheros de propiedades y XML y el uso de las normas estándar para la lectura de ficheros de configuración de Spring Framework.
7.    Puede utilizarse NFS con SAN para entornos clúster de repositorio compartido (solo a partir de la versión 3.4 según Alfresco) aunque es recomendable la utilización de sistemas de ficheros de clúster/concurrencia como GFS, OCFS, VxFS, etc.
8.    Los índices de Apache-Lucene deben ir siempre en el sistema local o en su defecto en NAS. A partir de la versión 4 de Alfresco ECM podrá utilizarse Solr.
9.    Es preferible tener en el “extension” una copia de log4j.properties como custom-log4j.properties para gestionar la salida de información de los logs.
10.    Aunque no es parte de Alfresco ECM, hay que medir cuidadosamente los aplicativos que interactúan con este respecto a seguridad:

  • a.    Uso de SSL (HTTPS) para asegurar canales de comunicación. Si es posible, también entre los propios elementos de Alfresco ECM, como Alfresco Share y el repositorio.
  • b.    Configuración de sistemas de autenticación externa mediante CAS, AD-Kerberos, NTLM, etc. y usar Single Sign On (SSO) en la medida de lo posible.
  • c.    Usar puertos por encima del 1024 y usuario “no root” en las instalaciones en sistemas Linux/Unix.

11.    No es recomendable balancear los protocolos TCP como CIFS/SMB, FTP y NFS que Alfresco ECM ofrece como servicios debido a problemas de bloqueos en JLan hasta aviso de Alfresco ECM y por lo menos hasta la versión actual (3.4.x). Sí es posible balancear HTTP/HTTPS y WebDAV. Una posible arquitectura siguiendo esta recomendación sería la siguiente:

12.    NO USAR JAMÁS EL USUARIO ROOT, crear un usuario tomcat, alfresco, jboss, etc. con los privilegios apropiados (acordarse de que en máquinas con Linux/Unix/MacOS X no pueden usarse puertos por debajo del 1024 por defecto).
13.    Es muy aconsejable que siempre se realicen 3 tipos de instalación, una para la parte de desarrollo y personalización, otra para preproducción o “Quality Service” que sirva para realizar pruebas antes de desplegar en producción, y una tercera instalación para producción. El entorno de desarrollo puede ser “no cluster” siempre y cuando no dependa dicho desarrollo de elementos própios de este. El entorno de preproducción y producción deben ser totalmente idénticos excepto en el tema de arquitectura hardware, es decir, puede ser un entorno con máquinas virtuales en preproducción y con máquinas físicas o virtuales con mayor asignación de recursos en producción. Así mismo, la carga de datos entre preproducción y producción tiene que ser lo más parecida posible, siempre al menos de un 50% de carga entre uno y otro para no tener problemas posteriores en cuanto a límites.

Fase de configuración y tuning
1.    Comprobar que la configuración de la codificación tanto en el SO, la SGDB, sistema de ficheros y JVM están en UTF-8. Ejemplos:

  • a.    En JVM de SUN y JRockit de IBM: -Dfile.encoding=UTF8
  • b.    En MySQL (my.cnf): default-character-set=utf8
  • c.    En Oracle, debe realizarlo un DBA.

2.    Usar autenticación con Single Sign On (SSO) en lo posible a través de AD-Kerberos o CAS siendo CAS el recomendable actualmente.
3.    Establecer los parámetros de monitorización en el arranque (JMX)
Ejemplo:
   Para monitorizar con jconsole: jconsole service:jmx:rmi:///jndi/rmi://servidoralfresco:50500/alfresco/jmxrmi
 
4.    Verificar parámetros de configuración y optimización del SGBD siempre a través de personal DBA certificado. Por ejemplo:

  • a.    MySQL: ANALYZE   
  • b.    PostgreSQL: VACUUM y ANALYZE
  • c.    Oracle: Dependiente de la versión, debe realizarse por un DBA.
  • d.    MS-SQL Server: ALTER INDEX REBUILD, UPDATE STATISTICS
  • e.    DB2: REORGCHK, RUNSTATS

5.    Utilizar los parámetros de optimización aconsejados por Alfresco:

  • a.    Ajustar pool de conexiones de Alfresco, se recomiendan 225 en adelante para el uso de protocolo CIFS/SMB. En WebSphere, Tomcat, Jboss, etc. Se pueden gestionar las conexiones a través de JNDI. En este caso, hay que tener en cuenta si el que controla los parámetros de conexiones máximas, mínimas, tiempos de espera para cierre de conexiones, etc. es Alfresco ECM o el mismo servidor de aplicaciones. Por ejemplo para WebSphere se pueden modificar los valores correspondientes desde la Consola de administración, en Recursos->JDBC->Orígenes de datos->(origen)->Propiedades de la agrupación de conexiones.

   db.pool.max=275

  • b.    Deshabilitar el uso de máximo de conexiones abiertas, es decir, no espera un tiempo en los que la conexión no responde para cerrarla. Igual que en el punto anterior, hay que modificarlo en la consola de administración de WebSphere o ficheros necesarios en otros servidores de aplicaciones.

   db.pool.idle=-1

  • c.    Esteblecer un tamaño de consultas (registros) mayor al establecido por defecto (10 registros).

   hibernate.jdbc.fetch_size=150

  • d.    Desactivar la parte de almacenamiento de transacción atómica para los índices y las transacciones de indexación “atómicas”, SOLO EN EL CASO DE IMPORTACIONES Y SUBIDAS MASIVAS DE DOCUMENTOS.

   lucene.maxAtomicTransformationTime=0
   index.tracking.disableInTransactionIndexing=true

  • e.    Si no se van a usar “quotas” de espacio, se aconseja desconectarlas ya que suponen tiempo de cálculo.

   system.usages.enables=false

  • f.    Usar JodConverter en lugar de la integración directa con OpenOffice.org deshabilitando esta última ya que si no Alfresco levantará dos instancias de OpenOffice.org. También es aconsejable usar un servidor independiente para realizar todas las conversiones.

   ooo.enabled=false
   jodconverter.enabled=true

  • g.    En clúster usar JGroups con conexiones TCP (para controlar mejor las conexiones).
  • h.    En clúster usar el “tracking” cada 5 segundos.

   index.tracking.cronExpression=0/5 * * * * ?

  • i.    En sistemas con muchos documentos y consultas Apache-Lucene muy genéricas, el resultado puede contener muchas filas y tardar mucho tiempo. Para evitar que salgan menos filas de las solicitadas hay que adaptar los parámetros system.acl.maxPermissionCheckTimeMillis y system.acl.maxPermissionChecks, pe. Para la salida de hasta 30000 filas cuya consulta dura menos de 5 minutos sería:

   system.acl.maxPermissionCheckTimeMillis=300000
   system.acl.maxPermissionChecks=30000

  • j.    Si es necesario indexar todo el contenido del documento y este tiene más de 10000 términos, hay que ajustar el valor lucene.indexer.maxFieldLength para que indexe todo el contenido. Por ejemplo, para que indexe contenidos con hasta 150000 palabras:

   lucene.indexer.maxFieldLength=150000

  • k.    Configurar correctamente y verificar su acceso a las utilidades utilizadas por Alfresco ECM:

   ImageMagick 
   Pdf2swf
   OpenOffice.org

  • l.    Realizar pruebas de carga y estrés antes de la puesta a producción mediante herramientas especializadas, p.e. JMeter.
  • m.    Adaptar valores de EHCache a los nodos, usuarios, permisos (ACLs), tickets de autenticación, etc. para que no se llene.

Desarrollo y personalización
Extensión del modelo de datos
1.    Usar la indexación por tokens en los casos necesarios, p.e. para metadatos que usan caminos, códigos o palabras sin significado semántico es mejor usar solamente la indexación por cadenas (strings).

Por ejemplo, si se tiene un metadato llamado “sección” que almacena una sección en particular como valor único, se podría definir como:


        d:text
       
        true
        false
        false
       
 

2.    Utilizar ficheros independientes por modelo de datos así como de prefijos para clarificar los desarrollos.
3.    NO usar nunca los modelos de ejemplo que vienen en Alfresco ECM.
4.    Es preferible usar Aspectos a Tipos e intentar crear tipos básicos heredados de los que Alfresco ECM incluye por defecto.
5.    Usar solamente los metadatos que van a ser usados en búsquedas en el gestor documental directamente y que tengan relevancia dentro de la gestión documental.
6.    Usar restricciones donde hagan falta (CONSTRAINTS) y reutilizarlas.
7.    Evitar en la medida de lo posible muchas asociaciones (ASSOCIATIONS), ya que Alfresco ECM no es un sistema relacional.
8.    No cambiar el modelo original de Alfresco ECM bajo ningún concepto.
9.    No eliminar modelos y aspectos si no se tiene total seguridad de que no han sido usados nunca.
10.    Evitar complejidades innecesarias en los modelos así como excesiva profundidad en la estructura.
11.    El modelo de datos dedicado a permisos y roles no puede ser movido del lugar del despliegue actualmente y hay que ser muy cauto a la hora de crear nuevos roles.
12.    Se aconseja no modificar los roles actuales.

Interoperabilidad
1.    Usar CMIS a través de RESTful principalmente (versión 3.4 en adelante) o en su defecto WebServices a través de SOAP para mantener la interoperabilidad, escalabilidad y estandarización.
2.    Comunicarse a través de aplicaciones mediante tecnologías SOA. Capas intermedias de middelware, buses de integración, fachadas de servicios y sistemas centralizados de control.
3.    Usar las AFC (Alfresco Foundation Classes) solo en casos muy específicos.
4.    Si es necesario personalizar/desarrollar directamente en Alfresco ECM, es preferible realizarlo a través de WebScripts, Reglas/Acciones y Workflows en lugar de desarrollar directamente clases Java.
5.    Es desaconsejado el uso de JCR ya que está obsoleto.

WebScripts/JavaScripts – Surf
1.    Incluir los ficheros de scripts en los lugares adecuados del extensión en lugar del despliegue o en su defecto crear un módulo de Alfresco ECM para instalarse de forma limpia usando el mmt (Module Management Tool) de Alfresco ECM.
2.    Usar librerías comunes mediante “includes” y reutilizar código.
3.    Realizar depuraciones mediante el depurador incluido en Alfresco ECM.
4.    Evitar mucha recursividad al recorrer nodos ya que puede llenar la memoria de pila, o bien, ampliar el espacio de esta en la configuración.
5.    Dirigir los desarrollos hacia la plataforma Spring-Surf.

WebServices
1.    Evitar la transferencia de grandes ficheros mediante mensajes SOAP. Usar para ello el Servlet que incorpora Alfresco ECM.
2.    Minimizar las transferencias de información en tareas reiterativas. Por ejemplo, es preferible la llamada a un WebScript que devuelva en formato JSON/Atom/Text la lista de usuarios que realizar N llamadas desde el WebService cliente.

Búsquedas y consultas
1.    Adecuar los motores de búsqueda de Alfresco ECM al tipo de consulta y resultado requerido XPath/Lucene/CMIS-SQL.
2.    Es recomendable ir hacia consultas vía CMIS (cmis-strict) para estandarizar lo máximo posible.
3.    Intentar optimizar las consultas Lucene/CMIS para que devuelva pocos resultados.

Autenticación y seguridad
1.    Usar alf_ticket como método de mantener sesiones autenticadas en lugar de el uso de autenticaciones usuario/contraseña contínuas, así como JSESSIONID para el caso de mantener rutas en balanceadores. Se recomienda legar el uso de autenticaciones a Alfresco ECM y sistemas dedicados a esta tarea como CAS, AD-Kerberos, etc.
2.    Almacenar los datos “sensibles” de forma “ofuscada” o encriptada.

Mantenimiento
1.    Monitorizar la JVM, Servidor de Aplicaciones y la instancia de Alfresco ECM mediante Jconsole/VisualVM, IBM WebSphere Console, etc.
2.    Realizar seguimientos de tráfico entre los componentes de Alfresco ECM (WebClients y Repositorio, Alfresco y SGBD, etc.) para detectar grandes cargas, tráfico alto y cuellos de botella usando comandos como ntop, iostat, netstat, etc.
3.    Reindexar todo cuando se hayan cambiado valores de configuración de Apache-Lucene así como si se detecta corrupción en los índices. Esto es muy importante para mantener estable el sistema. Se puede usar la consola de chequeo de índices de Alfresco ECM para reindexar por fechas, transacciones, etc. Por ejemplo: http://servidoralfresco:8080/alfresco/service/enterprise/admin/indexcheck
4.    Estudiar las salidas (logs) constantemente prestando especial atención a mensajes de aviso (Warnings) y errores (Errors) y filtrando convenientemente:

Ejemplo:
Salida controlada de errores de log en un WebSphere: tail -2000f /opt/WebSphere/AppServer70/profiles/AppSrv01/logs/alfresco/SystemOut.log | grep ” E “

5.    Utilizar herramientas como NAGIOS/ICINGA para monitorizar puertos, memoria, CPU, etc.
6.    Usar sistemas de mensajes SMS y alertas de seguimiento en los sistemas en producción.

Bibliografía
Documentos y libros
Título: Alfresco Day Zero Configuration Guide.pdf
Autor: Peter Monks

Título: Administering_an_Alfresco_Enterprise_3_4_0_Production_Environment.pdf
Autor: Alfresco

Título: Escalabilidad y tuning.pdf
Autor: Toni de la Fuente

Título: Scale your Alfresco Solutions. Architecture, Design and Tuning Best Practices
Autor: Gabriele Columbro

Titulo: Alfresco Developer Guide
Autor: Jeff Potts

Blogs:
http://www.blyx.com
http://www.fegor.com
http://ecmarchitect.com/

Webs:
http://docs.alfresco.com
http://www.juntadeandalucia.es/xwiki/bin/view/MADEJA/ArqSIAlfresco