Gestión documental usando CMIS - OpenCMIS y Chemistry

Si has llegado hasta aquí es porque estás involucrado en un proyecto/aplicación que maneja uno o varios documentos, que van a ser almacenados en el Gestor Documental (Alfresco). Por lo tanto, antes de seguir leyendo debes asegurarte de si el documento o documentos en cuestión DEBEN guardarse en el Archivo Electrónico, consultando con Daniel Sánchez (extensión 9531) y/o Jesús Jiménez Re (extensión 8753).

Ten en cuenta que TODOS los DOCUMENTOS de un proceso ADMINISTRATIVO de la UMU tienen que ser caracterizados (INVENTARIADOS) por parte del responsable funcional y almacenados en el Archivo Electrónico, en cumplimiento de la Política de Gestión de Documentos Electrónicos de la UM.

CMIS (Content Management Interoperability Services) es un estándar OASIS diseñado por los principales fabricantes de ECM (Enterprise Content Management) que permite acceder a los sistemas de gestión de contenidos de una forma normalizada. Con CMIS, por tanto, nos abstraemos del sistema de gestión de contenidos que esté dispuesto en una determinada organización a la hora de desarrollar nuestro software garantizando una integración sin problemas en cualquier gestor que implemente dicho estándar.

Figura:Gestores de contenidos que implementan CMIS

Apache Chemistry es una colección de librerías diseñadas para el uso de CMIS desde Java de una manera sencilla para los desarrolladores. Este conjunto de librerías incluye un cliente para comunicarse con gestores de contenidos mediante CMIS, y un servidor para ser el propio proveedor de dicha capa de servicios sobre la que interactuar.

Fundeweb incluye la capa cliente, version 1.1, que es la que nos permitirá realizar las búsquedas, subidas y modificaciones de documentos sobre nuestro gestor documental. Para esto haremos uso de la librería Apache Chemistry que nos permitirá gestionar desde Java todas las acciones que queramos realizar con nuestros documentos.

El requisito indispensable es tener actualizado el framework a la versión 2.0.24 (Windows 64 bits).

Aplicaciones FundeWeb 1.5

Antes de comenzar el desarrollo de nuestra aplicación con CMIS, deberemos configurar las librerías de Apache Chemistry en nuestro proyecto. Abrimos el fichero weblogic-application.xml y añadimos la siguiente definición:

<?xml version="1.0" encoding="UTF-8"?>
<weblogic-application
	xmlns="http://xmlns.oracle.com/weblogic/weblogic-application"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/javaee_5.xsd
		http://xmlns.oracle.com/weblogic/weblogic-application http://xmlns.oracle.com/weblogic/weblogic-application/1.5/weblogic-application.xsd">
 
    ...
 
    <library-ref>
        <library-name>chemistry-opencmis</library-name>
        <specification-version>1.0</specification-version>
        <implementation-version>EAR</implementation-version>
        <exact-match>true</exact-match>
    </library-ref>
 
    ...
 
</weblogic-application>

Ahora, deberemos cambiar la versión del <parent> en el POM principal del proyecto y cambiamos la versión por 1.5.24 o posterior.

También tenemos que añadir en el POM del módulo EJB las siguientes dependencias:

	<!-- OpenCMIS - Chemistry -->
	<dependency>
		<groupId>org.apache.chemistry.opencmis</groupId>
		<artifactId>chemistry-opencmis-client-impl</artifactId>
	</dependency>
 
	<dependency>
		<groupId>org.alfresco.cmis.client</groupId>
		<artifactId>alfresco-opencmis-extension</artifactId>
	</dependency>
	<!-- FIN - OpenCMIS - Chemistry -->

Aplicaciones FundeWeb 2.0 desplegadas en Weblogic 12.1 (FundeWeb IDE 2.0)

Abrimos el fichero weblogic.xml y añadimos la siguiente definición:

<weblogic-web-app
	xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd
		http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.5/weblogic-web-app.xsd">
 
    ...
 
    <library-ref>
        <library-name>chemistry-opencmis</library-name>
        <specification-version>1.0</specification-version>
        <implementation-version>WAR</implementation-version>
        <exact-match>true</exact-match>
    </library-ref>
 
    ...
 
</weblogic-web-app>

Ahora, deberemos cambiar la versión del <parent> en el POM principal del proyecto y cambiamos la versión por 2.0.26 o posterior.

También tenemos que añadir en el POM del módulo WEB las siguientes dependencias:

	<!-- OpenCMIS - Chemistry -->
	<dependency>
		<groupId>org.apache.chemistry.opencmis</groupId>
		<artifactId>chemistry-opencmis-client-impl</artifactId>
	</dependency>
 
	<dependency>
		<groupId>org.alfresco.cmis.client</groupId>
		<artifactId>alfresco-opencmis-extension</artifactId>
	</dependency>
	<!-- FIN - OpenCMIS - Chemistry -->

Aplicaciones FundeWeb 2.0 desplegadas en Weblogic 12.2 (FundeWeb IDE 2.1)

Abrimos el fichero weblogic.xml y añadimos la siguiente definición:

<weblogic-web-app
	xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd
		http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.9/weblogic-web-app.xsd">
 
    ...
 
    <library-ref>
        <library-name>chemistry-opencmis-fdw2</library-name>
        <specification-version>1.1</specification-version>
        <implementation-version>1.1.UMU</implementation-version>
        <exact-match>true</exact-match>
    </library-ref>
 
    ...
 
</weblogic-web-app>

Ahora, deberemos cambiar la versión del <parent> en el POM principal del proyecto y cambiamos la versión por 2.0.26 o posterior.

También tenemos que añadir en el POM del módulo WEB las siguientes dependencias:

	<!-- OpenCMIS - Chemistry -->
	<dependency>
		<groupId>org.apache.chemistry.opencmis</groupId>
		<artifactId>chemistry-opencmis-client-impl</artifactId>
	</dependency>
 
	<dependency>
		<groupId>org.alfresco.cmis.client</groupId>
		<artifactId>alfresco-opencmis-extension</artifactId>
	</dependency>
	<!-- FIN - OpenCMIS - Chemistry -->

Despliegue en Entorno NO-Local

Para poder desplegar la aplicación en los servidores de desarrollo, pre-producción (test) y producción, tenemos que tener desplegada la librería compartida en el nodo donde esta desplegada la aplicación. Un posible JIRA se muestra a continuación:

Proyecto DJ-AT-MIDDLEWARE WEB
Asunto Despliegue de la libreria Chemistry OpenCMIS en el nodo de la aplicacion <mi_aplicacion>
Texto del JIRA Necesitamos tener desplegado en el nodo que tiene desplegada la aplicación <mi_aplicacion>, la librería compartida Chemistry OpenCMIS para todos los entornos.

Apache Chemistry nos permite dos tipos de conexiones con Alfresco-CMIS: AtomPub binding y Web Services binding. En nuestro caso utilizaremos el punto de acceso Atom ya que es más eficiente que el enlace a través de la capa de servicios web.

El punto de acceso de la api Atom en el gestor documental Alfresco se encuentra en:

http(s)://urlgestordocumental:puerto/alfresco/api/-default-/public/cmis/versions/1.1/atom
 
Ejemplo alfresco desarrollo: http://gdocumpruebas.um.es/alfresco/api/-default-/public/cmis/versions/1.1/atom

Nota: Por defecto en la UMU sólo hay creado un repositorio de Alfresco para todos los contenidos, por lo que no tenemos que conocer el ID de dicho repositorio, simplemente recuperar el único dado de alta.

Un ejemplo de clase utilidad para conectar con CMIS es el siguiente:

@Name("utilsAlfrescoCmis")
public class UtilsAlfrescoCmis {
 
    @Logger
    private Log log;
 
    private Properties propiedades;	
 
    public static UtilsAlfrescoCmis getInstance(){
        if (!Contexts.isEventContextActive()) {
            throw new IllegalStateException("No active event context");
        }
 
        UtilsAlfrescoCmis instance = (UtilsAlfrescoCmis) Component.getInstance(UtilsAlfrescoCmis.class, ScopeType.EVENT);
 
        if (instance == null) {
            throw new IllegalStateException("No UtilsAlfrescoCmis could be created");
        }
 
        return instance;
    }
 
    /**
    * Carga las propiedades de conexion a Alfresco para CMIS.
    */
    @Create
    public void create() {
        try {
            this.setPropiedades( UtilsAlfrescoCmis.class.getClassLoader()
                                                        .getResourceAsStream( "alfresco.properties" ) );
            log.debug( "Se han cargado las propiedades del Servicio Alfresco" );
        }
        catch ( IOException e ) {
            log.error( "Se ha producido un error al cargar las propiedades de Alfresco:", e );
        }
    }
 
    /**
    * Devuelve la url de conexion con Alfresco segun el fichero de propiedades cargado.
    * En este caso hemos definido un fichero de propiedades con los valores del mismo.
    * Hay que tener en cuenta poder manejar diferentes entornos: desarrollo, preproduccion y produccion 
    */      
    private String getAtomPubURL() {
        return "http://" + propiedades.getProperty( "host" ) + ":" + propiedades.getProperty( "puerto" )
               +"/"+ propiedades.getProperty( "alfresco" ) + "/api/-default-/public/cmis/versions/1.1/atom";
    }
 
    private void setPropiedades( InputStream is ) throws IOException {
        propiedades = new Properties();
        propiedades.load( is );
    }
 
    /**
    * Crea una sesion de alfresco en base al usuario y password establecido
    */
     public Session abrirSesionCmis( String user, String password ) {
        SessionFactory factory = SessionFactoryImpl.newInstance();
        Map<String, String> parametros = new HashMap<String, String>();
        //TODO: Cambiar por método de desencriptado de contraseña correspondiente
        String passwordDesencriptado= DesCifra3DESClave.descifraPasswrd(password);
        // Credenciales
        parametros.put( SessionParameter.USER, user );
        parametros.put( SessionParameter.PASSWORD, passwordDesencriptado );
 
        // parametros de conexion
        parametros.put( SessionParameter.ATOMPUB_URL, getAtomPubURL() );
        parametros.put( SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value() );        
 
        List<Repository> repositorios = factory.getRepositories( parametros);        
        parametros.put( SessionParameter.COOKIES, "true" );
        parametros.put( SessionParameter.REPOSITORY_ID, repositorios.get( 0 ).getId() );            
        parametros.put(SessionParameter.OBJECT_FACTORY_CLASS, "org.alfresco.cmis.client.impl.AlfrescoObjectFactoryImpl");
 
        return factory.createSession( parametros );
 
    }
 
}

Una vez que tenemos establecida ya una sesión CMIS ya podemos operar con normalidad

@Name( "servicioCmisAlfresco" )
public class ServicioCmisAlfresco {
 
    @Logger
    private Log log;
 
    private static final String USUARIO_ALFRESCO = "MI_USUARIO";
 
    public static ServicioCmisAlfresco getInstance() {
        if (!Contexts.isEventContextActive()) {
            throw new IllegalStateException("No active event context");
        }
 
        ServicioCmisAlfresco servicio = (ServicioCmisAlfresco) Component.getInstance(ServicioCmisAlfresco.class, ScopeType.EVENT);
 
        if (servicio == null) {
            throw new IllegalStateException("No ServicioCmisAlfresco could be created");
        }
 
        return servicio;
    }
 
    /**
    * La ruta en el repositorio debe empezar y terminar por "/", el formato es el mismo que las rutas en un sistema operativo
    * Por ejemplo: /MiCarpeta/MiSubdirectorio/
    */
    public  String agregaDocumento( String tipoDocumento, String nombreDocumento,
            byte[] datosDocumento, String mimeType, Map<String, Object> metadatos,
            String ruta ) throws AutenticacionException,
            TipoDocumentoNoSoportadoException,
            ServicioAlfrescoUmuException {
 
        log.info( "Comenzando la subida del documento: #0", nombreDocumento );
 
        UtilsAlfrescoCmis util = UtilsAlfrescoCmis.getInstance();
        Session sesion = util.abrirSesionCmis( USUARIO_ALFRESCO, getPasswordAlfresco() );
 
 
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put( PropertyIds.OBJECT_TYPE_ID, tipoDocumento );
        properties.put( PropertyIds.NAME, nombreDocumento );
 
        if ( metadatos != null ) {
            for ( String clave : metadatos.keySet() ) {
                properties.put( clave, metadatos.get( clave ) );
            }
        }
 
        CmisObject directorioContenedor = sesion.getObjectByPath( ruta );
 
        InputStream stream = new ByteArrayInputStream( datosDocumento );
        ContentStream contentStream = new ContentStreamImpl( nombreDocumento,
                                                             BigInteger.valueOf( datosDocumento.length ), mimeType,
                                                             stream );
 
        Document nuevoDocumento= ( ( Folder ) directorioContenedor ).createDocument( properties, contentStream,
                                                                              VersioningState.MAJOR );
 
        log.info( "Finalizada la subida del documento: #0", nombreDocumento );
        return nuevoDocumento.getId();
    }
 
    /** 
    * Para recuperar un documento si no indicamos la versión devolverá la última
    */
    public  Document obtenDocumento( String uuid ) throws AutenticacionException,
            DocumentoNoEncontradoException,
            ServicioAlfrescoUmuException {
        log.info( "Comenzando la recuperación del fichero: #0", uuid );
        UtilsAlfrescoCmis util = UtilsAlfrescoCmis.getInstance();
        Session sesion = util.abrirSesionCmis( USUARIO_ALFRESCO, getPasswordAlfresco() );
        Document document = ( Document ) sesion.getObject( uuid );
        log.info( "Fin de la recuperación del fichero: #0", uuid );
        return document;
    }
 
    /** 
    * El uuid del documento está compuesto por su uuid y el numero de version separados por ";"
    * Por ejemplo, para borrar un documento, su version 1.0 deberemos pasar como uuid
    * 87a7be20-4cde-4a17-b62e-5098b7366ee1;1.0
    */ 
    public  void borrarDocumento(  String uuid ) throws AutenticacionException,
            DocumentoNoEncontradoException,
            ServicioAlfrescoUmuException {
        log.info( "Comenzando el borrado del fichero: #0", uuid );
        UtilsAlfrescoCmis util = UtilsAlfrescoCmis.getInstance();
        Session sesion = util.abrirSesionCmis( USUARIO_ALFRESCO, getPasswordAlfresco() );
        ObjectId id = new ObjectIdImpl( uuid );        
        sesion.delete( id );
        log.info( "Fin del borrado del fichero: #0", uuid );
    }
 
 
    public  void modificaMetadatos( String uuid, Map<String, Object> metadatos ) throws AutenticacionException,
            DocumentoNoEncontradoException,
            ServicioAlfrescoUmuException {
        log.info( "Comenzando la modificacion de metadatos del fichero: #0", uuid );
        Document documento = obtenDocumento( uuid );
        Map<String, Object> properties = new HashMap<String, Object>();
        if ( metadatos != null ) {
            for ( String clave : metadatos.keySet() ) {
                properties.put( clave, metadatos.get( clave ) );
            }
        }
        documento.updateProperties( properties );
        log.info( "Fin de la modificacion de metadatos del fichero: #0", uuid );
    }
 
 
    private  String getPasswordAlfresco() {
        //Recuperar el password de nuestro usuario en alfresco
        return password;
    }
 
}

Nota: En la documentación de Chemistry no se indica explicitamente que las sesiones se tengan que cerrar, ya que deben cerrarse de manera automática tras cada petición. No obstante si queremos asegurarnos de que nuestra sesión queda cerrara para optimizar recursos deberemos hacer:

  ...
 sesion.clear();
 sesion=null;
  ...

El servicio de acceso documental implementado en la librería de utilidades ServiciosAtica (que va a ser dado de baja) accede mediante la API SOAP de Alfresco, la cual ya no estará disponible en futuras versiones.

Esta API devuelve y gestiona la información de manera diferente a como lo hace CMIS por lo que deberemos tener varios aspectos en cuenta a la hora de migrar las aplicaciones que usaran este servicio. A continuación repasamos los aspectos que son diferentes en CMIS a la API SOAP.

Tipos de objetos

CMIS distingue el tipo de objeto que queremos manejar a partir de su prefijo, así pues usaremos

  1. D: Para referirnos a documentos.
  2. F: Para referirnos a un directorio.
  3. P: Para referirnos a un aspecto.

Por otro lado, a la hora de indicar el tipo documental con el que estamos trabajando. Mientras que en la API SOAP era encesario especificar en namespace completo ({urn:documento:namespace:nombre}TipoDocumento, en CMIS sólo hay que especificar el prefijo de dicho namespace.

Si tenemos un namespace urn:documento:namespace:nombre con preficjo pre el formato a indicar sería:

  1. API SOAP: {urn:documento:namespace:nombre}TipoDocumento
  2. API CMIS: D:pre:TipoDocumento

Metadatos

Los metadatos al igual que pasa con los tipos de objetos, no necesitan el namespace completo sino el prefijo, por lo tanto el id de un metadato en CMIS está formado por su preijo y el nombre del metadato. Por ejemplo pre:TipoUsuario.

Cuando subamos un documento con metadatos o modifiquemos los existentes debemos especificar el metadato con el tipo de objeto que lo representa y no transformarlo a un String como se hace en la API SOAP (en desuso).

Así que, por ejemplo si tenemos una fecha, deberemos pasar un objeto de tipo Date.

El Id del Documento

Con la API SOAP el ID de un documento se representaba por su UUID simplemente, en CMIS un documento está representado por su uuid y su número de versión, por lo que tendremos que tener esto en cuenta tanto para recuperarlo como para borrarlo.

Con la API SOAP, al subir un documento nos devolvía su uuid, por ejemplo: cdca39aa-4d45-4f3a-9fc4-2a9627b449a6

Con la API CMIS nos devuelve una composición con uuid y versión: cdca39aa-4d45-4f3a-9fc4-2a9627b449a6;1.0

Para recuperar un documento bastará con conocer el UUID, CMIS nos devolverá la última versión.

Para borrar un documento tendremos que añadir tanto el UUID como la versión.

Ruta del documento

Con la API SOAP la ruta del documento en el repositorio se daba de alta como una lista de array de strings, indicando directorio y prefijo del mismo.

	List<String[]> rutaEspacios = new ArrayList<String[]>();
	String[] espacio = { "cm", "directorio" };
	String[] subespacio = { "cm", "subdirectorio" };
	rutaEspacios.add(espacio);
	rutaEspacios.add(subespacio );

En cambio en CMIS las rutas para los directorios se establecen en el formato de rutas de un sistema operativo: /directorio/subdirectorio.

Un documento se crea a partir de la ruta del documento padre que lo contiene (o el root):

  CmisObject directorioContenedor = sesion.getObjectByPath( ruta );
  Document nuevoDocumento= ( ( Folder ) directorioContenedor ).createDocument( properties, contentStream,VersioningState.MAJOR );
  • fdw2.0/fundeweb2.0/gt/fdw-gt-cmis.txt
  • Última modificación: 09/11/2020 10:54
  • por JOSE JAVIER MIRA FERNANDEZ