====== 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** [[bpm:arc:archivado_electronico|Archivo Electrónico]], **consultando con** [[danielsm@um.es|Daniel Sánchez]] (extensión 9531) y/o [[jesus.jimenez@ticarum.es|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** [[https://wiki.um.es/wikis/eadministracion/doku.php?id=servicios:guia:archivo:introduccion|Archivo Electrónico]], en **cumplimiento** de la [[https://sede.um.es/sede/gestion-documental/inicio.seam|Política de Gestión de Documentos Electrónicos de la UM]]. ===== ¿Qué es CMIS? ===== 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. {{ :fdw:cmis.png |Figura:Gestores de contenidos que implementan CMIS}} ===== ¿Qué es Apache Chemistry? ===== 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 [[https://chemistry.apache.org/ | Apache Chemistry]] que nos permitirá gestionar desde Java todas las acciones que queramos realizar con nuestros documentos. ===== Configuración ===== 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: ... chemistry-opencmis 1.0 EAR true ... Ahora, deberemos cambiar la versión del //// 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: org.apache.chemistry.opencmis chemistry-opencmis-client-impl org.alfresco.cmis.client alfresco-opencmis-extension ==== 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: ... chemistry-opencmis 1.0 WAR true ... Ahora, deberemos cambiar la versión del //// 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: org.apache.chemistry.opencmis chemistry-opencmis-client-impl org.alfresco.cmis.client alfresco-opencmis-extension ==== 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: ... chemistry-opencmis-fdw2 1.1 1.1.UMU true ... Ahora, deberemos cambiar la versión del //// 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: org.apache.chemistry.opencmis chemistry-opencmis-client-impl org.alfresco.cmis.client alfresco-opencmis-extension ==== 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 | | **Texto del JIRA** | Necesitamos tener desplegado en el nodo que tiene desplegada la aplicación , la librería compartida Chemistry OpenCMIS para todos los entornos. | ===== Conexión con el proveedor de contenidos ===== 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 parametros = new HashMap(); //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 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 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 properties = new HashMap(); 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 metadatos ) throws AutenticacionException, DocumentoNoEncontradoException, ServicioAlfrescoUmuException { log.info( "Comenzando la modificacion de metadatos del fichero: #0", uuid ); Document documento = obtenDocumento( uuid ); Map properties = new HashMap(); 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; ... ===== Diferencias con la API de ServiciosAtica ===== 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 - **D**: Para referirnos a documentos. - **F**: Para referirnos a un directorio. - **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: - API SOAP: {urn:documento:namespace:nombre}TipoDocumento - 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 rutaEspacios = new ArrayList(); 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 ); ===== Bibliografía ===== * [[https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=cmis|OASIS CMIS]] * [[http://wiki.alfresco.com/wiki/CMIS|Alfresco CMIS Wiki]] * [[https://chemistry.apache.org/java/opencmis.html|OpenCMIS]] * [[http://chemistry.apache.org/|Apache Chemistry]] ---- --- //[[pedro.delgado@ticarum.es|PEDRO DELGADO YARZA]] 2017/04/03 09:33//