AnsweredAssumed Answered

HistoricVariableUpdate.getTaskId() always returns null

Question asked by naag on May 30, 2012
Latest reply on Jun 15, 2012 by naag
Dear all!

I'm working on a Activiti 5.9 based platform (JBoss AS 7, Java EE 6, JSF 2, CDI, …) and would like to archive a process instance when it ends. This archive should include all process variables with their history information and also the tasks that were executed (history=full is set in activiti.cfg.xml). What I've done so far:

  • Setup a ExecutionListener named ProcessEndArchiveListener

  • Attached this listener to my test process

  • Call my class ProcessArchiver from the listener

  • Created a @BusinessProcessScoped bean which is referenced in JSF forms related to the test process

  • The process is started / completed using BusinessProcessBean.startTask() / .completeTask() with a conversation
When the process ends, the ProcessEndArchiveListener is called successfully, and in ProcessArchiver I use the HistoryService to retrieve a list of HistoricVariableUpdate, but the method HistoricVariableUpdate.getTaskId() always returns null. Also in the database table ACT_HI_DETAIL, I can see that TASK_ID_ is always null. Other information like the variable content and revision is available, also the processInstanceId and the executionId.

This is the process:


<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="Test processes">
  <process id="test-process" name="Test process">
    <extensionElements>
      <activiti:executionListener event="end" class="foo.bar.ProcessEndArchiveListener"></activiti:executionListener>
    </extensionElements>
    <startEvent id="startevent1" name="Start" activiti:initiator="initiator"></startEvent>
    <userTask id="usertask1" name="Enter information" activiti:assignee="${initiator}" activiti:formKey="enter.jsf"></userTask>
    <sequenceFlow id="flow1" name="" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
    <userTask id="usertask2" name="Update information" activiti:assignee="${initiator}" activiti:formKey="update.jsf"></userTask>
    <sequenceFlow id="flow2" name="" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow3" name="" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
  </process>
</definitions>

The ExecutionListener:

public class ProcessEndArchiveListener implements ExecutionListener {

   public void notify(DelegateExecution execution) throws Exception {
      DestructibleBeanInstance<ProcessArchiver> processArchiverBean = null;
      try {
         processArchiverBean = BeanLookup.getBean(ProcessArchiver.class);
         ProcessArchiver processArchiver = processArchiverBean.getInstance();         
         processArchiver.archive(execution);
      } finally {
         if (processArchiverBean != null) {
            processArchiverBean.destroy();
         }
      }
   }   
}

The class used to access the HistoryService and do the archiving (this is where the taskId is null):


@ApplicationScoped
public class ProcessArchiver {

   @Inject
   protected ProcessEngine processEngine;

   public void archive(DelegateExecution execution) {
      HistoryService historyService = processEngine.getHistoryService();

      // …

      List<HistoricDetail> historicDetails = historyService.createHistoricDetailQuery()
            .processInstanceId(execution.getProcessInstanceId()).variableUpdates().list();
      List<HistoricVariableUpdate> historicVariableUpdates = new ArrayList<HistoricVariableUpdate>();
      for (HistoricDetail historicDetail : historicDetails) {
         if (historicDetail instanceof HistoricVariableUpdate) {
            // historicDetail.getTaskId() is always null here
            historicVariableUpdates.add((HistoricVariableUpdate) historicDetail);
         }
      }
      
      // …
   }
}

And also the @BusinessProcessScoped bean used in the process:


@Named
@BusinessProcessScoped
public class TestProcessBean implements Serializable {
   private static final long serialVersionUID = 1L;

   protected String title;
   protected String comment;

   // Getters & setters
}

Last but not least the activiti.cfg.xml:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd">

  <!– lookup the JTA-Transaction manager –>
  <bean id="transactionManager" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:jboss/TransactionManager"></property>
    <property name="resourceRef" value="true" />
  </bean>

  <!– process engine configuration –>
  <bean id="processEngineConfiguration" class="org.activiti.cdi.CdiJtaProcessEngineConfiguration">
    <property name="dataSourceJndiName" value="java:/DB/Activiti" />
    <property name="databaseType" value="mysql" />
    <property name="transactionManager" ref="transactionManager" />
    <property name="transactionsExternallyManaged" value="true" />
    <property name="databaseSchemaUpdate" value="true" />
    <property name="history" value="full" />

    <property name="customPostBPMNParseListeners">
      <list>
        <bean class="org.activiti.cdi.impl.event.CdiEventSupportBpmnParseListener" />
      </list>
    </property>
  </bean>
</beans>

I've debugged this situation a bit, but was lost in the Activiti Engine code after a while. What I've found out is that there's a call to VariableScopeImpl.initializeVariableInstanceBackPointer() when completing a task, and since VariableScopeImpl is abstract, this will actually be a call to ExecutionEntity or TaskEntity. In this method, the executionId and the processInstanceId will be set, and also the taskId, but only in TaskEntity implementation.

But my VariableScopeImpl is always an instance of ExecutionEntity, never TaskEntity, and that's why (I think) the taskId is never set. Am I making a mistake while using @BusinessProcessScoped? Do I need to take any special measures to ensure that the taskId is maintained?

If there's no direct answer, I would also be happy about some pointers to debug this further :-)

Many thanks for all your thoughts!

Regards,
Peter

Outcomes