Monday, May 5, 2014

Spring Managed Alfresco Custom Activiti Java Delegates

I'm using Alfresco 4's activiti workflow engine.  I recently needed to make a change to have activiti call an object managed by Spring instead of a class that is called during execution.  Couple of reasons for this:
  1. A new enhancement was necessary to access a custom database table, so I needed to inject a DAO bean into the activiti serviceTask.
  2. Refactoring of the code base was needed.  Having Spring manage the java delegate service task versus instantiating new objects for each process execution is always a better way to go, if the application is already Spring managed (which Alfresco is).  
    1. i.e. I needed access to the DAO bean and alfresco available spring beans.  
    2. NOTE:  You now have to make sure your class is thread safe though!
  3. Another side-effect is it's a little bit easier to write junit tests when using Spring dependency injection than instantiating new objects inside methods (take a look at one of my older posts about that).  
For a tutorial on Alfresco's advanced workflows with activiti, take a look at Jeff Pott's tutorial here.  This blog will only discuss what was refactored to have Spring manage the activiti engine java delegates.

I wanted to piggy-back off of the activiti workflow engine that is already embedded in Alfresco 4, so decided not define our own activiti engine manually.  The Alfresco Summit 2013 had a great video tutorial, which helped immensely to refactor the "Old Method" to the "New Method", described below.

Example:
For our example, we'll use a simple activiti workflow that defines two service tasks, CherryJavaDelegate and ShoeJavaDelegate (The abstract AbstractCherryShoeDelegate is the parent).  The "Old Method" does NOT have spring managing the activiti service task java delegates.  The "New Method" has spring manage and inject the activiti service task java delegates, and also adds an enhancement for both service tasks to write to a database table.

Old Method:
  1. Notice that the cherryshoebpmn.xml example below is defining the serviceTask's to use the "activiti:class" attribute; this will have activiti instantiate a new object for each process execution:

  2. <process id="cherryshoeProcess" name="Cherry Shoe Process" isExecutable="true">
        ...
        <serviceTask id="cherryTask" name="Insert Cherry Task" activiti:class="com.cherryshoe.activiti.delegate.CherryJavaDelegate"></serviceTask>
        
        <serviceTask id="shoeTask" name="Insert Shoe Task" activiti:class="com.cherryshoe.activiti.delegate.ShoeJavaDelegate"></serviceTask>
        ...
    </process>
    

  3. Since we have multiple service tasks that need access to the same activiti engine java delegate, we defined an abstract class that defined some of the functionality.  The specific concrete classes would provide / override any functionality not defined in the abstract class. 

  4. ...
    import org.activiti.engine.delegate.JavaDelegate;
    ...
    public abstract class AbstractCherryShoeDelegate implements JavaDelegate {
    ...
        @Override
        public void execute(DelegateExecution execution) throws Exception {
        ...
        }
    ...
    }
    
    public class CherryJavaDelegate extends AbstractCherryShoeDelegate {
    ...
    ...
    }
    
New Method:
Here's a summary of all that had to happen to have Spring inject the java delegate Alfresco 4 custom activiti service tasks (tested with Alfresco 4.1.5) and to write to database tables via injecting DAO beans.
  1. The abstract AbstractCherryShoeDelegate class extends activiti engine's BaseJavaDelegate
  2. There are class load order issues where custom spring beans will not get registered.  Set up depends-on relationship with the activitiBeanRegistry for the AbstractCherryShoeDelegate abstract parent
  3. The following must be kept intact:
    • In the spring configuration file, 
      • Abstract AbstractCherryShoeDelegate class defines parent="baseJavaDelegate" abstract="true" depends-on="activitiBeanRegistry"
      • For each concrete Java Delegate:
        • The concrete bean id MUST to match the class name, which in term matches the activiti:delegateExpression on the bpmn20 configuration xml file 
          • NOTE: Reading this alfresco forum looks like the activitiBeanRegistry registers the bean by classname, not by bean id, so likely this is not a requirement
        • The parent attribute MUST be defined as an attribute

    Details below:
  1. Define spring beans for the abstract parent class AbstractCherryShoeDelegate and each concrete class that extends AbstractCherryShoeDelegate (i.e. CherryJavaDelegate and ShoeJavaDelegate). Have spring manage the custom activiti java delegates where the concrete class.  The abstract parent must define it's own parent as "baseJavaDelegate", abstract="true", and depends-on="activitiBeanRegistry".

  2. <bean id="AbstractCherryShoeDelegate" parent="baseJavaDelegate" abstract="true" depends-on="activitiBeanRegistry"></bean>
        
    <bean id="CherryJavaDelegate"
    class="com.cherryshoe.activiti.delegate.CherryJavaDelegate" parent="AbstractCherryShoeDelegate">
        <property name="cherryDao" ref="com.cherryshoe.database.dao.CherryDao"/>
    </bean>
    
    <bean id="ShoeJavaDelegate"
    class="com.cherryshoe.activiti.delegate.ShoeJavaDelegate"  parent="AbstractCherryShoeDelegate">
        <property name="shoeDao" ref="com.cherryshoe.database.dao.ShoeDao"/>
    </bean>
    

    Note below won't work!
    - Do NOT put any periods to denote package structure in the bean id!  Alfresco/Activiti got confused by the package ".", where spring normally works fine with this construct.
    - Also just because the concrete class is extending the parent abstract class, is not enough to make it work.

    <bean id="com.cherryshoe.activiti.delegate.CherryJavaDelegate"
    class="com.cherryshoe.activiti.delegate.CherryJavaDelegate" >
        <property name="cherryDao" ref="com.cherryshoe.database.dao.CherryDao"/>
    </bean>
    
    <bean id="com.cherryshoe.activiti.delegate.ShoeJavaDelegate"
    class="com.cherryshoe.activiti.delegate.ShoeJavaDelegate" >
        <property name="shoeDao" ref="com.cherryshoe.database.dao.ShoeDao"/>
    </bean>
    

  3. Notice that the cherryshoebpmn.xml example below is using the "activiti:delegateExpression" attribute and referencing the spring bean.  This means only one instance of that Java class is created for the serviceTask it is defined on, so the class must be implemented with thread-safety in mind:

  4. <process id="cherryshoeProcess" name="Cherry Shoe Process" isExecutable="true">
        ...
        <serviceTask id="cherryTask" name="Insert Cherry Task" activiti:delegateExpression="${CherryJavaDelegate}"></serviceTask>
    
        <serviceTask id="shoeTask" name="Insert Shoe Task" activiti:delegateExpression="${ShoeJavaDelegate}"></serviceTask>
        ...
    </process>
    

  5. The abstract class is now changed to extend the BaseJavaDelegate.  The specific concrete classes would provide / override any functionality not defined in the abstract class. 

  6. ...
    import org.alfresco.repo.workflow.activiti.BaseJavaDelegate;
    ...
    public abstract class AbstractCherryShoeDelegate extends BaseJavaDelegate {
    ...
        @Override
        public void execute(DelegateExecution execution) throws Exception {
        ...
        }
    ...
    }
    
    public class CherryJavaDelegate extends AbstractCherryShoeDelegate {
    ...
    }
    
    

For more examples and ideas, I encourage you explore the links provided throughout this blog. Also take a look at activiti's user guide, particularly the Java Service Task Implementation section. What questions do you have about this post? Let me know in the comments section below, and I will answer each one.

No comments:

Post a Comment

I appreciate your time in leaving a comment!