lunes, 29 de agosto de 2011

Alfresco - Combinando WebScripts con Acciones (parte 3)

Y por ultimo, os dejo unas capturas de los pasos realizados...

1. Creamos un contenido de tipo Documento



2. Le asociamos uno o varios documentos






3. Una vez tenemos los documentos asociados, nos sale la opción para poder verlos



4. Hacemos click y nos muestra una nueva pantalla con la lista de asociados


Y esto ha sido todo por esta vez... espero que os resulte útil!

Alfresco - Combinando WebScripts con Acciones (parte 2)

Ya tenemos creado el webscript, el siguiente paso será crear la acción que lo ejecutará. Además, vamos a necesitar una pantalla de diálogo donde se mostrarán los documentos asociados.

Para empezar modificaremos el fichero web-client-config-custom.xml:

Primero mostramos las asociaciones en las propiedades del documento.

  
         
          
            
         
       
    
        
                 
        
       

    
          
              
         
    

Definimos la acción:

  
    

            
            es.pruebas.evaluator.DocumentosAsociadosEvaluator
           /images/icons/search.gif          
            #{VerDocsAsociadosDialog.setupAction}
            dialog:verDocsAsociadosDialog          
            
                
/sample/docsAsociados
                
Mostrar documentos asociados
                
#{actionContext.nodeRef}
            
        
    
            
       
        
           
        
         
           
         



las funciones de las distintas etiquetas son:

<evaluator> define una clase Evaluator que controla cuando mostrar la acción
<image> icono que se mostrará para la acción
<action-listener>, <action> definimos que es lo que se ejecutará
<params> parámetros que necesitamos en nuestra acción (en nuestro caso la clase diálogo)

Definimos el diálogo:

 
        
           
           
       
    
  


Y por último, registramos el diálogo en alfresco\WEB-INF\faces-config-custom.xml

      
         
            VerDocsAsociadosDialog
            es.pruebas.dialogs.VerDocsAsociadosDialog
           session
        
    


Las dos clases que hemos utilizado son:

VerDocsAsociadosDialog.java

  public class VerDocsAsociadosDialog extends BaseDialogBean {

    /**
     *
     */
    private static final long serialVersionUID = -653745991045332155L;
    private String url;
    private String title;
    private String webscriptUrl;

    private static Log log = LogFactory.getLog(VerDocsAsociadosDialog.class);

    @Override
    protected String finishImpl(FacesContext arg0, String arg1) throws Throwable {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void setupAction(ActionEvent event) {
        UIActionLink link = (UIActionLink) event.getComponent();
        Map params = link.getParameterMap();
     
        NodeRef nodo = new NodeRef(params.get("nodeRef"));      
        String url2 = params.get("webscript-url");     
        String title2 = params.get("title");
     
        if (!"".equals(url2)) {
            this.url = url2 + "?q="+ nodo.toString();
            this.title = title2;       
        }
    }

    @Override
    public String getContainerTitle(){
        return this.title;
    }

    @Override
    public String getCancelButtonLabel(){
        return "Cerrar";
    }
  
    @Override
    public String cancel() {      
        
        return getDefaultCancelOutcome();
    }

    public String getWebscriptUrl() {     
      
        return "/wcservice" + url;
    }

}
 DocumentosAsociadosEvaluator.java

   public class DocumentosAsociadosEvaluator implements ActionEvaluator{
       
        private static Log log = LogFactory.getLog(DocumentosAsociadosEvaluator.class);
        public static final QName ASOCIACION_DOC = QName.createQName("custom.model", "docsAsociados");

        public boolean evaluate(Node node) {

            NodeRef ref = new NodeRef(Repository.getStoreRef(), node.getId());
            FacesContext context = FacesContext.getCurrentInstance();
                   NodeService nodeSvc = Repository.getServiceRegistry(context).getNodeService();

            boolean result = false;      

           List asocs = (List)node.getAssociations().get(ASOCIACION_DOC);


            if(asocs != null && asocs.size() > 0) {
                        return true;
             }

            return result;

        }

        public boolean evaluate(Object arg0) {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    } 


y la jsp que mostrará los resultados verDocsAsociados.jsp

  <%@ taglib uri="/WEB-INF/repo.tld" prefix="r" >
    

    

Alfresco - Combinando WebScripts con Acciones (parte 1)

Hola de nuevo!
Tenia un poco olvidado el blog y he decidido retomarlo con este tutorial. La idea es combinar diferentes aspectos de Alfresco, como son las acciones, dialogos, webscripts, evaluadores...
Vamos a empezar creando un webscript muy sencillo, su función será mostrarnos los documentos que tiene asociados un documento en concreto. Para ello, primero debemos definir nuestro customModel.xml:
      
         
	Custom Model       
	      
	1.0 
	             
		 
		 
		
	    
	    
		
	           
		    
			     
				Documento      
				cm:content         
				  
					     
					Documentos Asociados          
					        
						false       
						true      
											
					               
						cm:content    
						false       
						true          
					         
					         
				   
			      
		  	

Un webscript se puede componer de varios ficheros:
  • Documento descriptivo
  • Script de control (opcional)
  • Una o varias plantillas
  • Documento de configuración (opcional)
  • Fichero de propiedades (opcional)

En nuestro caso, crearemos tres ficheros en la ruta:
/Company Home/Data Dictionary/Web Scripts/org/alfresco/sample

1. docsAsociados.get.desc.xml (documento descriptivo):
  

     Documentos Asociados  
     /sample/docsAsociados?q={searchTerm}   
	 extension  
	 user  
	 required  
 
2. docsAsociados.get.html.ftl (plantilla de salida)

<html>
  <body>     
    <br>
    <table>
	<tr><td colspan="2"><b><u>Documentos Asociados</u></b></td></tr>
		<tr></tr>
		<tr></tr>
		<tr style='background-color: #C6D8EB'>
			<td></td>
			<td align="center"><b>Nombre<b></td>				
		</tr>
     <tr>
		<#list resultset as node>		
			<td align="center"><img src="${url.context}${node.icon16}"/></td>
			<td align="center"><a href="/alfresco${node.url}" target="_blank">${node.name}</a></td>		
			<td></td>		
			<#if node.assocs["custom:docsAsociados"]?exists>
				<#list node.assocs["custom:docsAsociados"] as doc>				  				
					<td><a href="/alfresco${doc.url}" target="_blank">Documento</a></td>
					<td></td>
					<td align="center">${doc.name}</td>
					<td></td>							
				</#list>
			</#if>     
			<tr><td colspan="6">--------------------------------------------------------------------------------------</td></tr>
		</#list>
	</tr>
    </table>
  </body>
</html>

3. docsAsociados.get.js (script de control)
 
 if (args.q == undefined || args.q.length == 0){ 
	status.code = 400;  
	status.message = "Search term has not been provided.";  	
	status.redirect = true;
}	else{
    var node = search.findNode(args.q);  
	var asociaciones = node.assocs["custom:docsAsociados"]; 
	model.resultset = asociaciones;
}
Para información más completa y detallada acerca de los webscripts, aquí encontrareis todo lo necesario.

miércoles, 30 de marzo de 2011

Alfresco - Creando un workflow desde cero (parte 3)

En esta tercera y ultima parte explicaré como adaptar nuestro proceso para introducirlo en Alfresco.

Para ello necesitaremos los siguientes ficheros:

../tomcat/shared/classes/alfresco/extension/workflows

- processdefinition.xml 

../tomcat/shared/classes/alfresco/extension

- customWorkflowModel.xml
- custom-workflow-context.xml
- custom-workflow-messages.properties+
 
También podemos echar un vistazo a varios ejemplos en ../alfresco/WEB-INF/classes/alfresco/workflow

El modelo de contenido que he definido es el siguiente:
 




  
     
     
     
  

  
     
  
     
  
    
         bpm:startTask
         
            bpm:assignee           
          
    
    
         bpm:workflowTask
         
            
               edit_package_item_actions
            
           
                   
            mwf:motivos
         
    
    
        bpm:workflowTask
         
            bpm:assignee
         
    
    
         bpm:workflowTask
         
            bpm:assignee
            mwf:motivos
           
    
  
 
      
      
       
            
                Motivo Rechazo
               d:text                   
               false
                       
         
        
   


Hemos definido un tipo para cada uno de los task-node de nuestro workflow que heredan las propiedades de bpm:workflowTask.
El estado inicial mwf:submitApproveContent  será de tipo bpm:startTask que llevará las propiedades descripción, prioridad y fecha de vencimiento. 
Además, hemos creado un aspecto que tendrá como propiedad un motivo de rechazo.

custom-workflow-context.xml
 




    
        
                      
                
                    jbpm
                    alfresco/extension/workflows/processdefinition.xml
                    text/xml
                    true
                
            
        
        
            
                alfresco/extension/customWorkflowModel.xml
            
        
        
                        
               alfresco/extension/custom-workflow-messages
            
        
    

custom-workflow-messages.properties
 
#Workflow

mwf_approveContent.workflow.title=Aprobar Contenido
mwf_approveContent.workflow.description=Proceso para aprobar contenido

#Review & Approve Task Definitions

mwf_customworkflowmodel.type.mwf_submitApproveContent.title=Iniciar aprobaci\u00f3n  de contenido
mwf_customworkflowmodel.type.mwf_submitApproveContent.description=Enviar documentos para su revisi\u00f3n y aprobaci\u00f3n
mwf_customworkflowmodel.type.mwf_reviewContent.title=Revisar contenido
mwf_customworkflowmodel.type.mwf_reviewContent.description=Revisar documentos para aprobarlos o rechazarlos
mwf_customworkflowmodel.type.mwf_approvedContent.title=Contenido Aprobado
mwf_customworkflowmodel.type.mwf_approvedContent.description=Contenido Aprobado
mwf_customworkflowmodel.type.mwf_rejectedContent.title=Contenido Rechazado
mwf_customworkflowmodel.type.mwf_rejectedContent.description=Contenido Rechazado
 
 
 Y finalmente, hemos modificado processdefinition.xml 
 



      

    
                  
        
    

     
        
            #{bpm_assignee}
          
    

    
        
        
        
        
        
        
        
    

    
           
              
        
    

    
          
              
    

    

 

Los cambios realizados en la definición son:

Se han modificado los nombres de las tareas añadiendole la extensión de nuestro modelo de contenido (mwf) .

Se han añadido los elementos swimlane que se utilizan para asignar las tareas a un usuario o grupo de usuarios de Alfresco.

El rol initiatior será el encargado de iniciar y finalizar el flujo, siempre debe aparecer.

El rol assignee será el usuario encargado de realizar la tarea de aprobar/rechazar contenido. En mi caso es un único usuario, tal y como viene indicado por la etiqueta <actor>, pero también puede ser un grupo de usuarios, que podriamos definirlo utilizando la etiqueta <pooledactors>.

También se ha añadido un script sencillo que enviará un email a la persona que inició el flujo en caso de que el usuario haya rechazado el contenido.

Para que las propiedades se visualicen en nuestro web-client, tendremos que modificar el fichero web-client-config-custom.xml y añadir lo siguiente:


      
         ...
      
   
 
    
       
        ....
      
   
 
    
      
         ...
      
   
 
    
      
        ...  
      
   

Una vez tengamos todo, reiniciamos el servidor de Alfresco y ya tendremos nuestro workflow disponible!!!

Alfresco - Creando un workflow desde cero (parte 2)

Vamos a empezar creando un workflow sencillo en Eclipse que consistirá en: - Usuario A solicita la revisión de un documento a Usuario B. - Usuario B aprueba/rechaza el documento. - Usuario A es informado de la tarea realizada. Debemos copiar la libreria activation.jar en el directorio lib de la instalación de jBPM y a continuación crearemos un proyecto "Process Project":
 Y dentro de nuestro proyecto, un "Process Definition"
El diagrama podriamos dibujarlo de la siguiente manera:
El fichero processdefinition.xml  equivalente sería:


    
        
    

    
        
        
    

    
        
    

    
        
    

    


En un workflow siempre debemos tener los estados que definen el inicio (start-state) y fin (end-state) de nuestro proceso.

Alfresco - Creando un workflow desde cero (parte 1)

Hola!

He empezado esta semana a trabajar con workflows y me he llevado una sorpresa... parecen "divertidos" y al menos es diferente a lo que he estado haciendo hasta ahora!

He tenido que recopilar información de varios sitios distintos como la wikipedia o los foros... y finalmente he entendido su funcionamiento.

Me ha parecido buena idea crear este tutorial, desde la instalación de jbpm hasta la integración en Alfresco de un proceso sencillo. Creo que resultará util a gente que no haya hecho nunca un workflow en Alfresco...

Voy a dedicar esta primera parte del tutorial a la instalación y configuración de jBPM.

He decidido instalar la versión que recomiendan en la wiki, la jbpm-jpdl-3.2.6.SP1 (a lo mejor hay alguna más nueva pero a mi con esta me funciona bien).

Para instalar ejecutamos:

java –jar jbpm-installer-3.2.6.jar 

(Si durante la instalación nos pide directorio de instalación de JBOSS y no tenemos, introducimos C:/tmp)

Una vez hecho esto, podemos instalar jBPM process designer en nuestro Eclipse (en mi caso Helios).



En la siguiente ventana añadiremos un archivo local, seleccionaremos el fichero jbpm-jpdl-designer-site.zip que se encuentra en la carpeta designer de la instalación de jbpm.




En el menú window/preferences, en la sección JBoss jBPM/Runtime Location, añadiremos la ruta de la instalación de jbpm.
 


Para desplegar los procesos en Alfresco podremos hacerlo bien de forma manual, o bien desde el Eclipse, configurando los siguientes parámetros en la seccion "Server Deployment".










miércoles, 9 de marzo de 2011

Alfresco WebServices - Manipulando contenido

Cuando queramos hacer una apliación a medida en Java y trabajar con el repositorio de Alfresco, podremos usar, entre otras cosas, los webservices. Para ello, una de las librerias que necesitaremos es alfresco-web-service-client.jar, donde podemos encontrar los siguientes servicios:

AuthenticationService
RepositoryService
ContentService
AuthoringService
ClassificationService
ActionService
AccessControlService
AdministrationService
DictionaryService

Hoy me voy a centrar en la manipulación de contenido, veremos como crearlo, modificar propiedades, crear aspectos, crear asociaciones...

Lo primero que tenemos que hacer es autenticarnos en Alfresco:

AuthenticationUtils.startSession(USERNAME, PASSWORD);

Aprovecho esto para explicar brevemente como crear un pdf con jasperreports que posteriormente utilizaremos para crear nuestro nodo en el repositorio. Con la herramienta iReport podemos crear una plantilla para pdfs que nos generará dos ficheros, un *.jasper y otro *.jrxml, ambos ficheros deben estar dentro de nuestro proyecto. (prometo crear un post explicando esta herramienta más extensamente :-))

Para generar el pdf podriamos implementar el siguiente método:

public static byte[] obtenerPdf(String rutaReport) { byte[] pdf = null; try { //en ruta report se encuentran los ficheros necesarios que hemos mencionado anteriormente JasperReport jasperReport = (JasperReport) JRLoader.loadObject(new URL(rutaReport)); //el método fillReport nos da la posibilidad de añadir parámetros pero en este caso no es necesario JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, null); ByteArrayOutputStream pdfOutput = new ByteArrayOutputStream(); JRPdfExporter exporter = new JRPdfExporter(); exporter.setParameter(JRPdfExporterParameter.METADATA_AUTHOR, "autor"); exporter.setParameter(JRPdfExporterParameter.METADATA_CREATOR, "creador"); exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint); exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, pdfOutput); exporter.exportReport(); pdf = pdfOutput.toByteArray(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (JRException e) { // TODO Auto-generated catch block e.printStackTrace(); } return pdf; }


Comenzaremos ahora a crear nuestro contenido, para lo que necesitaremos los siguientes servicios:

RepositoryServiceSoapBindingStub repositoryService = WebServiceFactory.getRepositoryService();
ContentServiceSoapBindingStub contentService = WebServiceFactory.getContentService();


Primero tenemos que decidir donde lo vamos a guardar, podemos hacerlo bien en companyHome:

Store storeRef = new Store(Constants.WORKSPACE_STORE, "SpacesStore");
ParentReference companyHomeParent = new ParentReference(storeRef, null, "/app:company_home", Constants.ASSOC_CONTAINS, "nombrePdf");

o bien podemos indicarle el path de un espacio concreto donde lo queramos almacenar:

String pathEncoded = "/app:company_home"+"/cm:"+ISO9075.encode("Mis documentos");
Store storeRef = new Store(Constants.WORKSPACE_STORE, "SpacesStore");
ParentReference parentReference = new ParentReference(storeRef, null,pathEncoded, Constants.ASSOC_CONTAINS, "nombrePdf");


Le indicamos de que tipo será el contenido

ContentFormat contentFormat = new ContentFormat("application/pdf", "UTF-8");

modificamos sus propiedades, en este caso el nombre y la descripción:

NamedValue[] propiedades = new NamedValue[2]; propiedades[0] = Utils.createNamedValue(Constants.PROP_NAME, "nombrePdf"); propiedades[1] = Utils.createNamedValue(Constants.PROP_DESCRIPTION, "descripción del nuevo nodo");

y finalmente creamos el contenido:

CMLCreate create = new CMLCreate("1", parentReference, null, null, null, Constants.TYPE_CONTENT, propiedades); CML cml = new CML(); cml.setCreate(new CMLCreate[]{create}); UpdateResult[] result = repositoryService.update(cml);

Reference newContentNode = result[0].getDestination(); byte contentString[] = obtenerPdf(ruta); //Modificamos el contenido de nuestro nuevo nodo con el pdf que hemos obtenido contentService.write(newContentNode, Constants.PROP_CONTENT, contentString, contentFormat);

Ya tenemos creado nuestro nodo (newContentNode) y ahora podemos actualizar sus propiedades

CMLUpdate update = new CMLUpdate(); NamedValue[] props = new NamedValue[1]; props[0] = Utils.createNamedValue(Constants.PROP_DESCRIPTION, "esta es una modificación del nodo"); update.setProperty(props); update.setWhere(new Predicate(new Reference[]{newContentNode}, storeRef, null));
cml.setUpdate(new CMLUpdate[]{update});
repositoryService.update(cml);

y añadirle aspectos y asociaciones

CMLAddAspect addAspect = new CMLAddAspect();
addAspect.setAspect(Constants.ASPECT_VERSIONABLE);

addAspect.setWhere(new Predicate(new Reference[]{newContentNode}, storeRef, null));
cml.setAddAspect(new CMLAddAspect[]{addAspect});

CMLCreateAssociation createAssoc = new CMLCreateAssociation();
createAssoc.setAssociation(Constants.ASSOC_CHILDREN);
createAssoc.setFrom(Predicate)//Nodo hijo que hayamos creado o que ya exista en Alfresco
createAssoc.setTo(new Predicate(new Reference[]{newContentNode}, storeRef, null))//Nodo padre

cml.setCreateAssociation(new CMLCreateAssociation[]{createAssoc});

repositoryService.update(cml);

También podemos mover nuestro nodo a otro espacio

CMLMove move = new CMLMove();
//carpeta destino
move.setTo(companyHomeParent);
//nodo que vamos a mover
move.setWhere(new Predicate(new Reference[]{newContentNode}, storeRef, null));
cml.setMove(new CMLMove[]{move});
repositoryService.update(cml);

o bien borrarlo:

CMLDelete delete = new CMLDelete(new Predicate(new Reference[]{newContentNode}, storeRef, null));
cml.setDelete(new CMLDelete[]{delete});

repositoryService.update(cml);

Una vez hayamos terminado, no nos olvidemos nunca de finalizar la sesión:

AuthenticationUtils.endSession();