cancel
Showing results for 
Search instead for 
Did you mean: 

Historic Process instance variable is out of date for serialized Java object

pstapl
Champ in-the-making
Champ in-the-making
I am using Activit's forms to change the field values in a Serializable Java object as part of a process.
I have noticed that the historic process instance variables are often out of sync with the runtime version of the variables when the process has finished (and sometimes even during the process execution)

This causes a problem for me when I complete and submit the last form/user task in the process. The process ends and the only way to obtain the process variable is through the HistoryService API. This then returns an old version of the java object without the updates in the final form submission.

<!–break–>

Here is a simple testcase to demonstrate my problem. This demonstrates the problem in Activiti 5.19.0.

BPMN XML:


<?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:xsd="http://www.w3.org/2001/XMLSchema" 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="http://www.activiti.org/test">
  <process id="myProcess" name="My process" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <endEvent id="endevent1" name="End"></endEvent>
    <userTask id="usertask1" name="User Task">
      <extensionElements>
        <activiti:formProperty id="field1" name="Field1" type="string" expression="${myobject.field1}" required="true"></activiti:formProperty>
        <activiti:formProperty id="field2" name="Field2" type="string" expression="${myobject.field2}" required="true"></activiti:formProperty>
      </extensionElements>
    </userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="servicetask1"></sequenceFlow>
    <serviceTask id="servicetask1" name="Service Task" activiti:class="com.example.mytest.MyServiceTask"></serviceTask>
    <sequenceFlow id="flow4" sourceRef="servicetask1" targetRef="usertask1"></sequenceFlow>
    <userTask id="usertask2" name="User Task">
      <extensionElements>
        <activiti:formProperty id="field1" name="Field1" type="string" expression="${myobject.field1}" required="true"></activiti:formProperty>
        <activiti:formProperty id="field2" name="Field2" type="string" expression="${myobject.field2}" required="true"></activiti:formProperty>
      </extensionElements>
    </userTask>
    <sequenceFlow id="flow5" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
    <sequenceFlow id="flow6" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
  </process>
</definitions>


Referenced Service Task used to create an instance of MyBean and attach as a process instance variable:

package com.example.mytest;

import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;

public class MyServiceTask implements JavaDelegate {

   public void execute(DelegateExecution execution) throws Exception {
      execution.setVariable("myobject", new MyBean());
   }

}


The Java Bean being used.


package com.example.mytest;

import java.io.Serializable;

public class MyBean implements Serializable {

   /**
    *
    */
   private static final long serialVersionUID = 1L;

   private String field1;
   private String field2;

   public String getField1() {
      return field1;
   }

   public void setField1(String field1) {
      this.field1 = field1;
   }

   public String getField2() {
      return field2;
   }

   public void setField2(String field2) {
      this.field2 = field2;
   }

}


Finally the testcase to demonstrate the problem

package com.example.mytest;

import java.util.HashMap;
import java.util.Map;

import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.test.ActivitiRule;
import org.activiti.engine.test.Deployment;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;

public class ProcessTest {
   
   @Rule
   public ActivitiRule activitiRule = new ActivitiRule();
   
   @Deployment(resources = {"MyProcess.bpmn"})
   @Test
   public void test() {
      
      ProcessInstance pi = activitiRule.getRuntimeService().startProcessInstanceByKey("myProcess");
      
      Map<String, String> properties;
      Task task;
      MyBean myobject;
      
      properties = new HashMap<String, String>();
      properties.put("field1", "value1");
      properties.put("field2", "value2");
      
      task = activitiRule.getTaskService().createTaskQuery().processInstanceId(pi.getId()).singleResult();
      activitiRule.getFormService().submitTaskFormData(task.getId(), properties);
            
      properties = new HashMap<String, String>();
      properties.put("field1", "value3");
      properties.put("field2", "value4");
      
      task = activitiRule.getTaskService().createTaskQuery().processInstanceId(pi.getId()).singleResult();
      activitiRule.getFormService().submitTaskFormData(task.getId(), properties);
            
      Assert.assertEquals(0, activitiRule.getRuntimeService().createProcessInstanceQuery().count());
      myobject = (MyBean) activitiRule.getHistoryService().createHistoricVariableInstanceQuery().processInstanceId(pi.getId()).variableName("myobject").singleResult().getValue();
      Assert.assertEquals("value4", myobject.getField2());
   }

}


The result I get when running is:


org.junit.ComparisonFailure: expected:<value[4]> but was:<value[2]>
   at org.junit.Assert.assertEquals(Assert.java:115)
   at org.junit.Assert.assertEquals(Assert.java:144)
   at com.example.mytest.ProcessTest.test(ProcessTest.java:45)
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
   at java.lang.reflect.Method.invoke(Method.java:497)
   at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
   at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
   at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
   at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
   at org.activiti.engine.test.ActivitiRule$1.evaluate(ActivitiRule.java:126)
   at org.junit.rules.RunRules.evaluate(RunRules.java:20)
   at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
   at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
   at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
   at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
   at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
   at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
   at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
   at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
   at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
   at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
   at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)



I would have expected the value of field2 to have been set to 'value4' in the historic process instance variable, after I had submitted the second form. Instead it appears not to have been updated from 'value2'. As the process is finished I cannot check in the runtime process instance to see if that was updated or not.

I'm not sure if I am doing something wrong or how I can get access to a correctly up-to-date version of the historic process instance variable.
2 REPLIES 2

jbarrez
Star Contributor
Star Contributor
Does the runtime variable reflect the change? Ie. when you add another task after it, and you fetch the object, are the runtime values updated?

pstapl
Champ in-the-making
Champ in-the-making
The runtime variables seem to be updated correctly after all user tasks - this problem only seems to affect the historic variables.

I updated my example to contain a new User Task after the other two. It does not have any form properties. New BPMN XML is:

<code>
<?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:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlnsSmiley Surprisedmgdc="http://www.omg.org/spec/DD/20100524/DC" xmlnsSmiley Surprisedmgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
  <process id="myProcess" name="My process" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <endEvent id="endevent1" name="End"></endEvent>
    <userTask id="usertask1" name="User Task">
      <extensionElements>
        <activiti:formProperty id="field1" name="Field1" type="string" expression="${myobject.field1}" required="true"></activiti:formProperty>
        <activiti:formProperty id="field2" name="Field2" type="string" expression="${myobject.field2}" required="true"></activiti:formProperty>
      </extensionElements>
    </userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="servicetask1"></sequenceFlow>
    <serviceTask id="servicetask1" name="Service Task" activiti:class="com.example.mytest.MyServiceTask"></serviceTask>
    <sequenceFlow id="flow4" sourceRef="servicetask1" targetRef="usertask1"></sequenceFlow>
    <userTask id="usertask2" name="User Task">
      <extensionElements>
        <activiti:formProperty id="field1" name="Field1" type="string" expression="${myobject.field1}" required="true"></activiti:formProperty>
        <activiti:formProperty id="field2" name="Field2" type="string" expression="${myobject.field2}" required="true"></activiti:formProperty>
      </extensionElements>
    </userTask>
    <sequenceFlow id="flow5" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
    <sequenceFlow id="flow6" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
    <userTask id="usertask3" name="User Task"></userTask>
    <sequenceFlow id="flow7" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
  </process>
</definitions>
</code>

I then updated the testcase to retrieve both the runtime and historic variable and print it out after the first, second and third user task. (For the third task there is no runtime verison). New testcase is:

<java>
package com.example.mytest;

import java.util.HashMap;
import java.util.Map;

import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.test.ActivitiRule;
import org.activiti.engine.test.Deployment;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;

public class ProcessTest {

@Rule
public ActivitiRule activitiRule = new ActivitiRule();

@Deployment(resources = {"MyProcess.bpmn"})
@Test
public void test() {
 
  ProcessInstance pi = activitiRule.getRuntimeService().startProcessInstanceByKey("myProcess");
 
  Map<String, String> properties;
  Task task;
  MyBean myobject;
 
  properties = new HashMap<String, String>();
  properties.put("field1", "value1");
  properties.put("field2", "value2");
 
  task = activitiRule.getTaskService().createTaskQuery().processInstanceId(pi.getId()).singleResult();
  activitiRule.getFormService().submitTaskFormData(task.getId(), properties);
 
  System.out.println("After 1st User Task");
  // Retrieve and print historic variable
  myobject = (MyBean) activitiRule.getHistoryService().createHistoricVariableInstanceQuery().processInstanceId(pi.getId()).variableName("myobject").singleResult().getValue();
  System.out.println("Historic value - field1: " + myobject.getField1() + " field2: " + myobject.getField2());
  // Retrieve and print runtime variable
  myobject = (MyBean) activitiRule.getRuntimeService().getVariable(pi.getId(), "myobject", MyBean.class);
  System.out.println("Runtime value - field1: " + myobject.getField1() + " field2: " + myobject.getField2());
   
  properties = new HashMap<String, String>();
  properties.put("field1", "value3");
  properties.put("field2", "value4");
 
  task = activitiRule.getTaskService().createTaskQuery().processInstanceId(pi.getId()).singleResult();
  activitiRule.getFormService().submitTaskFormData(task.getId(), properties);
 
  System.out.println("After 2nd User Task");
  // Retrieve and print historic variable
  myobject = (MyBean) activitiRule.getHistoryService().createHistoricVariableInstanceQuery().processInstanceId(pi.getId()).variableName("myobject").singleResult().getValue();
  System.out.println("Historic value - field1: " + myobject.getField1() + " field2: " + myobject.getField2());
  // Retrieve and print runtime variable
  myobject = (MyBean) activitiRule.getRuntimeService().getVariable(pi.getId(), "myobject", MyBean.class);
  System.out.println("Runtime value - field1: " + myobject.getField1() + " field2: " + myobject.getField2());
 
 
  task = activitiRule.getTaskService().createTaskQuery().processInstanceId(pi.getId()).singleResult();
  activitiRule.getFormService().submitTaskFormData(task.getId(), properties);
 
  System.out.println("After 3rd User Task");
  // Retrieve and print historic variable
  myobject = (MyBean) activitiRule.getHistoryService().createHistoricVariableInstanceQuery().processInstanceId(pi.getId()).variableName("myobject").singleResult().getValue();
  System.out.println("Historic value - field1: " + myobject.getField1() + " field2: " + myobject.getField2());
  // Retrieve and print runtime variable
  // Does not work, as the process is finished. Returns ActivitiObjectNotFoundException
  //myobject = (MyBean) activitiRule.getRuntimeService().getVariable(pi.getId(), "myobject", MyBean.class);
  //System.out.println("Runtime value - field1: " + myobject.getField1() + " field2: " + myobject.getField2());

   
  Assert.assertEquals(0, activitiRule.getRuntimeService().createProcessInstanceQuery().count());
  myobject = (MyBean) activitiRule.getHistoryService().createHistoricVariableInstanceQuery().processInstanceId(pi.getId()).variableName("myobject").singleResult().getValue();
  Assert.assertEquals("value4", myobject.getField2());
}

}
</java>

The output from the execution gives:

<code>
After 1st User Task
Historic value - field1: null field2: null
Runtime value - field1: value1 field2: value2
After 2nd User Task
Historic value - field1: null field2: null
Runtime value - field1: value3 field2: value4
After 3rd User Task
Historic value - field1: value3 field2: value4
</code>

So it appears that after the first and second user task the historic process instance variable is not updated at all, but after the 3rd user task it is updated correctly. The runtime variable is always updated correctly.