Friday, July 29, 2011

Update Task variables from Alfresco Javascript API

Hi. Alfresco has it's own javascript API for workflow. U can read abaut this from alfresco wiki:
1. Alfresco Javascript API
2. Workflow Administration

U can read and write task variables in JBPM. The Alfresco JavaScript action lets you embed custom JavaScript into process definitions in much the same way that you can embed BeanShell scripts. To do so, simply add an <action> element with class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript" attribute. Inside the <action> element, include a <script> element that defines the JavaScript to execute. As with the BeanShell script definitions, this <script> element may optionally contain <variable> elements
and an <expression> element if you want to limit access to process and task variables.



In Alfresco u can read task properties from javascript api, for example
var taskId = "jbpm$12";
var task = workflow.getTask(taskId); //workflowTask

// Will print --> Description for taskId = jbpm$12 is AM-000046-2011 
// in my case of course :)
if (logger.isLoggingEnabled()){
   logger.log( "Description for taskId = "+taskId + " is " + task.properties["bpm:description"] );
}

But how we can update task variables in webscript (Not Java backed webscript - simple javascript) ?
When I first need this function I search in alfresco wiki and blogs, but can't find any solution for this problem except Java Backed Webscript :(
Then I decided to Add Custom Script API . And this made my work so easy:

/*
 * Read from http://wiki.alfresco.com/wiki/3.4_JavaScript_API#Adding_Custom_Script_APIs
 * 
 * This class helps u to update workflow task properties from your JS
 * 
 */

package az.gov.supremecourt.repo.jscript;

import org.alfresco.repo.jscript.ValueConverter;
import org.alfresco.repo.processor.BaseProcessorExtension;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mozilla.javascript.ScriptableObject;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Vasif Mustafayev
 */
public final class ScWorkflow extends BaseProcessorExtension {
 
 private static Log logger = LogFactory.getLog(ScWorkflow.class );
 
 /** Workflow Service */
 private WorkflowService workflowService;
 
    /** Node Value Converter */
    private ValueConverter converter = null;
    
    /** Registry Service */
 private ServiceRegistry serviceRegistry;
 
 /** Authentication Service */
 private AuthenticationService authenticationService;
 
 
 /**
  * Make WorkflowService aviable from spring context file
  * <property name="workflowService" ref="WorkflowService" />
  * 
  * @method setWorkflowService
  * @param workflowService
  */
 public void setWorkflowService(WorkflowService workflowService) {
     this.workflowService = workflowService;
 }
 
 
 /**
  * Make ServiceRegistry aviable from spring context file
  * <property name="serviceRegistry" ref="ServiceRegistry"/>
  * 
  * @method setServiceRegistry
  * @param serviceRegistry
  */
 public void setServiceRegistry(ServiceRegistry serviceRegistry) {
     this.serviceRegistry = serviceRegistry;
 }
 
 
 /**
  * Make AuthenticationService aviable from spring context file
  * <property name="authenticationService" ref="AuthenticationService"/>
  * 
  * @method setServiceRegistry
  * @param serviceRegistry
  */
 public void setAuthenticationService(AuthenticationService authenticationService) {
     this.authenticationService = authenticationService;
 }
 
 
 
 /**
  * Call this method from JS and set props for task
  * 
  * @method setTaskProperties
  * @param taskId
  * @param properties
  * @return
  */
 public boolean setTaskProperties(String taskId, Object properties) {
  
  boolean propertiesAdded = false;
  
  // if properties object is a scriptable object, then extract property name/value pairs
  // into property Map<QName, Serializable>, otherwise leave property map as null
  Map<QName, Serializable> workflowParameters = null;
        if (properties instanceof ScriptableObject) {
            ScriptableObject scriptableProps = (ScriptableObject)properties;
            workflowParameters = new HashMap<QName, Serializable>(scriptableProps.getIds().length);
            extractScriptablePropertiesToMap(scriptableProps, workflowParameters);
        }
        
        WorkflowTask workflowTask = workflowService.getTaskById(taskId);
        String currentUser = authenticationService.getCurrentUserName();

        // if the the current user is able to edit, updating the task is allowed
        if (this.workflowService.isTaskEditable(workflowTask, currentUser)) {
         // update task properties
         workflowTask = workflowService.updateTask(taskId, workflowParameters, null, null);
         
         propertiesAdded = (workflowTask != null)? true:false;         
        
        } else {
         if ( logger.isErrorEnabled() )
                logger.error("U haven't permission to edit this task ;)");
        }
        
        return propertiesAdded;
  
 }
 
 
 
 
 
 
  /**
     * Gets the value converter
     * 
     * @return the value converter
     */
    protected ValueConverter getValueConverter()
    {
        if (converter == null)
        {
            converter = new ValueConverter();
        }
        return converter;
    }
    
    
    /**
     * Helper to create a QName from either a fully qualified or short-name QName string
     * 
     * @param s Fully qualified or short-name QName string
     * 
     * @return QName
     */
    private QName createQName(String s)
    {
        QName qname;
        if (s.indexOf("" + QName.NAMESPACE_BEGIN) != -1)
        {
            qname = QName.createQName(s);
        }
        else
        {
            qname = QName.createQName(s, this.serviceRegistry.getNamespaceService());
        }
        return qname;
    }
 
 
 /**
     * Helper to extract a map of properties from a scriptable object (generally an associative array)
     * 
     * @param scriptable    The scriptable object to extract name/value pairs from.
     * @param map           The map to add the converted name/value pairs to.
     */
    private void extractScriptablePropertiesToMap(ScriptableObject scriptable, Map<QName, Serializable> map)
    {
        // get all the keys to the provided properties
        // and convert them to a Map of QName to Serializable objects
        Object[] propIds = scriptable.getIds();
        for (int i = 0; i < propIds.length; i++)
        {
            // work on each key in turn
            Object propId = propIds[i];
            
            // we are only interested in keys that are formed of Strings i.e. QName.toString()
            if (propId instanceof String)
            {
                // get the value out for the specified key - it must be Serializable
                String key = (String)propId;
                Object value = scriptable.get(key, scriptable);
                if (value instanceof Serializable)
                {
                    value = getValueConverter().convertValueForRepo((Serializable)value);
                    map.put(createQName(key), (Serializable)value);
                }
            }
        }
    }

 
}

Then u must export this class as jar and add it to alfresco lib folder (<tomcat webapp>/alfresco/WEB-INF/lib)

The next step is registration this class in spring configuration file (*-context.xml file is spring config file, u can create this file in <alfresco>/extension folder by any name with ending -context.xml exp: supremecourt-context.xml):

<!--  ================= Supreme Court Adding Custom Scripts =================  -->
<!-- Use 'scworkflow' object to update task variables -->
 <bean id="scWorkflowScript" parent="baseJavaScriptExtension" class="az.gov.supremecourt.repo.jscript.ScWorkflow">
  <property name="workflowService" ref="WorkflowService" />
  <property name="serviceRegistry" ref="ServiceRegistry"/>
  <property name="authenticationService" ref="AuthenticationService" />
  <property name="extensionName">
   <value>scworkflow</value>
  </property>
 </bean>

Now restart server.

The last step is using this object :)

var props = new Array();
var taskId = "jbpm$13";
props["bpm:description"] = "Your description value goes here:)";
props["today"] = new Date();
var added = scworkflow.setTaskProperties(taskId, props);
if (added) {
  logger.log("Your properties added successful");
}

It is so easy, isn't it ;)

8 comments:

  1. Thanks for this example! It helps me a lot!

    ReplyDelete
  2. Not at all :) I'm glad that it was helpful

    ReplyDelete
  3. Excellent pieces. Keep posting such kind of information on your blog. I really impressed by your blog.
    Vee Eee Technologies| Vee Eee Technologies|

    ReplyDelete
  4. Thanks.
    Einshtein: "Try not to become a man of success, but rather try to become a man of value"

    ReplyDelete
  5. The available workflows are those that have been registered with the JBPM engine.

    alfresco workflow

    ReplyDelete
  6. This is great code. Thank you very much for posting it.

    ReplyDelete
    Replies
    1. Do you know how to update the "initiator" of the workflow?

      Delete