cancel
Showing results for 
Search instead for 
Did you mean: 

workflow history

mythox
Champ on-the-rise
Champ on-the-rise
Hi, how may I check the workflow history when the task is ended?   Thankyou
25 REPLIES 25

sanket
Champ on-the-rise
Champ on-the-rise
Open this link at your local alfresco.

http://localhost:8082/alfresco/faces/jsp/admin/workflow-console.jsp

You will see a list of commands.
I think the command - "show my completed" is appropriate for your requirement.


Hope this helps,
Thanks

lucille_arkenst
Champ in-the-making
Champ in-the-making
This is cool for administrators.  But the users want to see something they can understand; not "id: jbpm$25…"  And they want to see a "show all completed".  Does somebody know how to accomplish this?

Thanks in advance!

nicolasraoul
Star Contributor
Star Contributor
Hello Lucile,

Workflow information is erased at the end of each workflow instance, so each workflow has to save its history somewhere before it completes.

My solution to do this is:
- At the last step of the workflow (PublishAction), query the workflow history and write it to a property of the published document
- The users can read this property to know the workflow history

Here is the code, just look for the parts that say "history":

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.transform.ContentTransformer;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.workflow.jbpm.JBPMNode;
import org.alfresco.repo.workflow.jbpm.JBPMSpringActionHandler;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FileNotFoundException;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
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.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jbpm.context.exe.ContextInstance;
import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.graph.exe.ProcessInstance;
import org.springframework.beans.factory.BeanFactory;

/**
* Publish a document.
*/
public class PublishActionHandler extends JBPMSpringActionHandler {

   private static final long serialVersionUID = 7663991590006420247L;

   private static final Log logger = LogFactory.getLog(PublishActionHandler.class);

   /**
    * QNames.
    */
   private static final QName PROP_QNAME_APPROVED = QName.createQName("http://www.someco.co.jp/", "approved");
   
   /**
    * Alfresco API Services.
    */
   private ServiceRegistry serviceRegistry;
   private WorkflowService workflowService;
   private NodeService nodeService;
   private NodeService unprotectedNodeService;
   private NamespaceService namespaceService;
   private SearchService searchService;
   private FileFolderService fileFolderService;
   private ContentService contentService;
   
   /**
    * Initialization.
    */
   @Override
   protected void initialiseHandler(BeanFactory factory) {
      serviceRegistry = (ServiceRegistry)
         factory.getBean(ServiceRegistry.SERVICE_REGISTRY);
      workflowService = serviceRegistry.getWorkflowService();
      nodeService = serviceRegistry.getNodeService();
      unprotectedNodeService = (NodeService)factory.getBean("nodeService");
      searchService = serviceRegistry.getSearchService();
      fileFolderService = serviceRegistry.getFileFolderService();
      contentService = serviceRegistry.getContentService();
      namespaceService = serviceRegistry.getNamespaceService();
   }

   /**
    * Publish the nodes referred to by the workflow.
    */
   @Override
   public void execute(ExecutionContext context) throws Exception {
      String publicationMethod = (String)context.getVariable("publicationMethod");
      // Run the following as system user, because we need to write the PDF in a protected place.
      AuthenticationUtil.setRunAsUserSystem();
      
      ProcessInstance processInstance = context.getProcessInstance();
      ContextInstance contextInstance = context.getContextInstance();
      FileFolderService fileFolderService = serviceRegistry.getFileFolderService();
      
      // Get the BPM package node reference.
      JBPMNode bpmPackage = (JBPMNode) contextInstance.getVariable("bpm_package");
      NodeRef packageNodeRef = bpmPackage.getNodeRef();
      List<ChildAssociationRef> childs = nodeService.getChildAssocs(packageNodeRef);
      
      // Process every file associated with this workflow instance.
      List<NodeRef> nodeRefs = new ArrayList<NodeRef>(); // Will be used by the PublishWorker.
      for (ChildAssociationRef childAssocRef : childs) {
         NodeRef original = childAssocRef.getChildRef();
         
         // Delete old published version if any.
           List<ChildAssociationRef> children = nodeService.getChildAssocs(original);
           // Delete the "approvedVersion" children, there should only be zero or one.
           for (ChildAssociationRef childAssoc : children) {
              QName qName = childAssoc.getQName();
              if(qName.equals(QName.createQName("http://www.someco.co.jp/", "approvedVersion"))) {
                 
                  NodeRef previouslyApprovedNodeRef = childAssoc.getChildRef();
                  nodeService.removeChild(original, previouslyApprovedNodeRef);
                  nodeService.removeAssociation(original, previouslyApprovedNodeRef, QName.createQName("http://www.someco.co.jp/", "approvedVersion"));
                  nodeService.removeAssociation(previouslyApprovedNodeRef, original, QName.createQName("http://www.someco.co.jp/", "approvedVersion"));
                fileFolderService.delete(previouslyApprovedNodeRef);
              }
           }
         
         // Publish approved version of the document(s).
         NodeRef published = publish(original, getDestinationSpace(original), publicationMethod);
         nodeRefs.add(published);
         
         // Set original document's type.
          nodeService.setType(original, QName.createQName("http://www.someco.co.jp/", "standardDocument"));

           // Set published document's type.
          nodeService.setType(published, QName.createQName("http://www.someco.co.jp/", "approvedDocument"));

          // Set original document's aspect.
          Map<QName,Serializable> aspectValues = new HashMap<QName,Serializable>();
           aspectValues.put(PROP_QNAME_APPROVED, true);
           nodeService.addAspect(published, QName.createQName("http://www.someco.co.jp/", "approvable"), aspectValues);

         // Link original to approved version.
            // The approved version becomes the child of the original.
            nodeService.addChild(original, published,
                  QName.createQName("http://www.someco.co.jp/", "approvedVersion"),
                  QName.createQName("http://www.someco.co.jp/", "approvedVersion"));

         // Link approved version to original
            // The approved version gets an association to the original.
            nodeService.createAssociation(published, original,
                  QName.createQName("http://www.someco.co.jp/", "originalVersion"));
      }
      // Read workflow history and write it to the published node's history property.
      // Must run in its own thread because of bug ACT-15024-16929
      PublishWorker worker = new PublishWorker(processInstance.getId(), nodeRefs, workflowService, nodeService);
      ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(5);
      ThreadPoolExecutor threadExecutor = new ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS, queue);
      threadExecutor.execute(worker);
   }

   /**
    * Publish a node into the destination space, in the given format.
    */
   private NodeRef publish(NodeRef original, NodeRef destinationSpace, String publicationMethod) throws FileExistsException, FileNotFoundException {
      if(publicationMethod.equals("PDF")) {
         return publishPDF(original, destinationSpace);
      }
      else {
         return publishCopy(original, destinationSpace);
      }
   }
   
   /**
    * Publish a node into the destination space as PDF.
    */
   private NodeRef publishPDF(NodeRef original, NodeRef destinationSpace) {
      ContentService contentService = serviceRegistry.getContentService();

      // Get original's MIME type.
      ContentData contentData = (ContentData) nodeService.getProperty(original, QName.createQName(
            "http://www.alfresco.org/model/content/1.0", "content"));
      String originalMimeType = contentData.getMimetype();

      // Get original's filename.
      String filename = (String) nodeService.getProperty(original, ContentModel.PROP_NAME);
      
      // Create the file.
      final QName contentQName = QName.createQName("{http://www.alfresco.org/model/content/1.0}content");
      FileInfo pdfInfo = fileFolderService.create(destinationSpace, filename + ".pdf", contentQName);
      NodeRef pdf = pdfInfo.getNodeRef();
      
      // Transform into PDF
      ContentReader reader = contentService.getReader(original, ContentModel.PROP_CONTENT);
       ContentWriter pdfWriter = contentService.getWriter(pdf, ContentModel.PROP_CONTENT, true);
       ContentTransformer pdfTransformer =
              contentService.getTransformer(originalMimeType, MimetypeMap.MIMETYPE_PDF);
       if(pdfTransformer == null)
          logger.error("No PDF transformer found for type " + originalMimeType);
         pdfTransformer.transform(reader, pdfWriter);

         return pdf;
   }

   /**
    * Copies a node into the destination space.
    */
   private NodeRef publishCopy(NodeRef original, NodeRef destinationSpace) throws FileExistsException, FileNotFoundException {
      String name = (String) nodeService.getProperty(original, ContentModel.PROP_NAME);
      FileFolderService fileFolderService = serviceRegistry.getFileFolderService();
      FileInfo fileInfo = fileFolderService.copy(original, destinationSpace, name);
      return fileInfo.getNodeRef();
   }
   
   /**
    * Find space where we will write the published document.
    * Create directories if necessary.
    */
   private NodeRef getDestinationSpace(NodeRef original) throws FileNotFoundException {
      // Get the folder in which the original is.
      ChildAssociationRef childAssociationRef = nodeService.getPrimaryParent(original);
       NodeRef originalSpace = childAssociationRef.getParentRef();

       List<FileInfo> originalPath = fileFolderService.getNamePath(null , originalSpace);
      
       NodeRef destinationWalker; // We will use this variable to walk along the tree and create it if necessary.
       boolean directoryInexistant = false;
       ResultSet resultSet = searchService.query(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore") ,
             SearchService.LANGUAGE_LUCENE, "PATH:\"/app:company_home/cm:承認後\"");
       try
       {
           if (resultSet.length() == 0)
           {
               throw new AlfrescoRuntimeException("Didn't find 承認後");
           }
           destinationWalker = resultSet.getNodeRef(0);
       }
       finally
       {
          resultSet.close();
       }
      
       for(int level=2; level<originalPath.size(); level++) {
          FileInfo originalDirectory = originalPath.get(level);
          String originalFilename = originalDirectory.getName();
          
          // Look for a child with this name in destination.
          NodeRef matchingChildNodeRef = null;
          if( ! directoryInexistant) {
             List<ChildAssociationRef> children = nodeService.getChildAssocs(destinationWalker);
              for (ChildAssociationRef childAssoc : children) {
                  NodeRef childNodeRef = childAssoc.getChildRef();
                  String childFilename = (String) nodeService.getProperty(childNodeRef, ContentModel.PROP_NAME);
                  if(childFilename.equals(originalFilename)) {
                     matchingChildNodeRef = childNodeRef;
                     break;
                  }
              }
          }
          
           // If no child with this name in destination, create it.
           if(matchingChildNodeRef == null) {
              FileInfo info = fileFolderService.create(destinationWalker, originalFilename, ContentModel.TYPE_FOLDER);
              matchingChildNodeRef = info.getNodeRef();
              directoryInexistant = true;
           }
          
           // Walk to the next level.
           destinationWalker = matchingChildNodeRef;
       }
      
       return destinationWalker;
   }
}

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.cmr.workflow.WorkflowTaskQuery;
import org.alfresco.service.cmr.workflow.WorkflowTaskState;
import org.alfresco.service.cmr.workflow.WorkflowTransition;
import org.alfresco.service.namespace.QName;
import org.alfresco.web.ui.common.Utils;

/**
* Worker used by PublishActionHandler.
* It is a workaround for https://issues.alfresco.com/jira/browse/ALF-2337
*
* TODO This workaround is far from perfect because there is a race condition
* between this thread and the PublishActionHandler thread.
*
* @author Nicolas Raoul
*
*/
public class PublishWorker implements Runnable {

   /**
    * Alfresco API Services.
    */
   private WorkflowService workflowService;
   private NodeService nodeService;

   /**
    * QNames.
    */
   private QName PROP_QNAME_HISTORY = QName.createQName("http://www.someco.co.jp/", "approvalHistory");
   
   /**
    * jBPM process id.
    */
   private long processId;
   
   /**
    * Nodes in which the history must be written.
    * In fact, there is probably only one node.
    */
   private List<NodeRef> nodeRefs;
   
   /**
    * Constructor.
    */
   public PublishWorker(long processId, List<NodeRef> nodeRefs, WorkflowService workflowService, NodeService nodeService) {
      this.processId = processId;
      this.nodeRefs = nodeRefs;
      this.workflowService = workflowService;
      this.nodeService = nodeService;
   }

   /**
    * Run the worker.
    */
   @Override
   public void run() {
      System.out.println("PublishWorker.run enter");
      
      // Read history from workflow.
      // Needs to be done fast, because the workflow is finishing in another thread,
      // and history is deleted when the workflow finishes.
      String history = readHistory();

      // Wait for the nodes to be written in the main thread, otherwise we get a InvalidNodeRefException.
      try {
         Thread.sleep(5000);
      } catch (InterruptedException e) {}
      
      // Write history to the nodes.
      final String finalHistory = history;
      for(NodeRef nodeRef : nodeRefs) {
         // Write workflow history to the published documents, as admin.
         final NodeRef finalNodeRef = nodeRef;
         RunAsWork<Object> work = new RunAsWork<Object>() {
              public Object doWork() throws Exception {
                 // Set the history property.
                 nodeService.setProperty(finalNodeRef, PROP_QNAME_HISTORY, finalHistory);
                 return new Object(); // Because we have to return something.
              }
         };
         AuthenticationUtil.runAs(work, "admin");
      }
      System.out.println("PublishWorker.run exit");
   }
   
   /**
    * Read history of a workflow instance.
    * @return history as a human-readable String.
    */
   private String readHistory() {
      
      // Build processId string.
        String processIdString = "jbpm$" + new Long(processId).toString();

      // Get completed tasks for current workflow instance.
      WorkflowTaskQuery query = new WorkflowTaskQuery();
        query.setProcessId(processIdString);
        query.setTaskState(WorkflowTaskState.COMPLETED);
        query.setOrderBy(new WorkflowTaskQuery.OrderBy[] {
                 WorkflowTaskQuery.OrderBy.TaskCreated_Desc,
                 WorkflowTaskQuery.OrderBy.TaskActor_Asc });
        List<WorkflowTask> completedTasks = workflowService.queryTasks(query);

      // Get in-progress tasks for current workflow instance.
        query.setTaskState(WorkflowTaskState.IN_PROGRESS);
        List<WorkflowTask> inProgressTasks = workflowService.queryTasks(query);
       
        // Join COMPLETED and IN_PROGRESS tasks.
        List<WorkflowTask> tasks = new ArrayList<WorkflowTask>();
        tasks.addAll(completedTasks);
        tasks.addAll(inProgressTasks);
       
        // Print the tasks to a String.
        StringBuffer historyBuffer = new StringBuffer();
        for (WorkflowTask task : tasks)
        {
           // Get task characteristics.
           Long id = (Long)task.properties.get(WorkflowModel.PROP_TASK_ID);
           String desc = (String)task.properties.get(WorkflowModel.PROP_DESCRIPTION);
           Date createdDate = (Date)task.properties.get(ContentModel.PROP_CREATED);
           String owner = (String)task.properties.get(ContentModel.PROP_OWNER);
           String comment = (String)task.properties.get(WorkflowModel.PROP_COMMENT);
           Date completedDate = (Date)task.properties.get(WorkflowModel.PROP_COMPLETION_DATE);
           String transition = (String)task.properties.get(WorkflowModel.PROP_OUTCOME);
           String outcome = "";
           if (transition != null)
           {
              WorkflowTransition[] transitions = task.definition.node.transitions;
              for (WorkflowTransition trans : transitions)
              {
                 if (trans.id.equals(transition))
                 {
                    outcome = trans.title;
                    break;
                 }
              }
           }
           // Write task characteristics.
           historyBuffer.append(id.toString());
           historyBuffer.append(" ; ");
           historyBuffer.append(desc == null ? "" : Utils.encode(desc));
           historyBuffer.append(" ; ");
           historyBuffer.append(Utils.encode(task.title));
           historyBuffer.append(" ; ");
           historyBuffer.append(createdDate);
           historyBuffer.append(" ; ");
           historyBuffer.append(owner == null ? "" : owner);
           historyBuffer.append(" ; ");
           historyBuffer.append(comment == null ? "" : Utils.encode(comment));
           historyBuffer.append(" ; ");
           historyBuffer.append(completedDate);
           historyBuffer.append(" ; ");
           historyBuffer.append(outcome);
           historyBuffer.append("\n");
        }
        return historyBuffer.toString();
   }   
}

It is quite tricky because of the workaround for bug ALF-2337.

Cheers,
Nicolas Raoul
http://nicolas-raoul.blogspot.com

lucille_arkenst
Champ in-the-making
Champ in-the-making
Thanks very much, Nicolas.  This will be my first time writing Java code in Alfresco.  Is it just a matter of importing the .war file into Eclipse, adding the code, and then exporting it?

nicolasraoul
Star Contributor
Star Contributor
No, it is actually not simple, the instructions are too long to paste here I think 😕
Read about Java actions for advanced workflows, in the wiki.
Good luck!

lucille_arkenst
Champ in-the-making
Champ in-the-making
Workflow information is erased at the end of each workflow instance
If you go to the workflow console and type "show my completed", it does show history (not user friendly, of course), but it shows it… so it looks like it isn't deleted completely.  Not sure yet, but this may help somehow.  Just wanted to share.

lucille_arkenst
Champ in-the-making
Champ in-the-making
I need to read about "Java actions for advanced workflows".  Is this the page were you were referring to?  http://wiki.alfresco.com/wiki/Packaging_And_Deploying_Extensions
Thanks again.

nicolasraoul
Star Contributor
Star Contributor
Indeed, there is nothing about it in the wiki, that's too bad  :cry:

It is explained here: http://docs.jboss.com/jbpm/v3/userguide/processmodelling.html#actions

One of the best available documentation is probably this PDF: http://ecmarchitect.com/images/articles/alfresco-workflow/advanced-workflow-article.pdf (from page 42)

Search the Web for examples that contain "ActionHandler", as all actions handlers will have this in the source code.

I had started preparing a blog post about calling web services in Java from workflows, but unfortunately I am afraid it won't be ready before a long time.

Good luck!
Nicolas Raoul

lucille_arkenst
Champ in-the-making
Champ in-the-making
I apologize, I didn't explain myself properly.  (But the jboss article was good!  And I did read PDF from ecmarchitect)…
What I wanted to know is – when I write Java code, how do I integrate it with Alfresco? 

But I think I just answered my own question… "SDK" (http://wiki.alfresco.com/wiki/Alfresco_SDK_3.3)