cancel
Showing results for 
Search instead for 
Did you mean: 

Signaling the event to process instance and all of its sub-processes

rmilovic
Champ in-the-making
Champ in-the-making
Hi. I'm wondering is it possible and how can I signal the event to a process instance and all of its sub-process instances?
12 REPLIES 12

rallen1
Confirmed Champ
Confirmed Champ
If your parent process instance is using a unique businessKey, this might work for you:

<java>
  List<Execution> executions = runtimeService.createExecutionQuery()
    .processInstanceBusinessKey(processInstance.getBusinessKey(), true)
    .signalEventSubscriptionName("readySignal")
    .list();
 
  for (Execution exec : executions)
  {
   runtimeService.signalEventReceived("readySignal", exec.getId());
  }
</java>

You can also use signalEventReceivedAsync to make the signal asynchronous.

Hope that helps.

rmilovic
Champ in-the-making
Champ in-the-making
Sorry rallen but this doesn't work for some reason. Maybe I'm doing something wrong. Here is the test that I wrote:

<java>
@Test
    public void test() {
     String businessKey = UUID.randomUUID().toString();
     ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testSignalProcess", businessKey);
    
     List<Execution> executions = runtimeService.createExecutionQuery()
       .processInstanceBusinessKey(businessKey, true).signalEventSubscriptionName("testSignal").list();
    
     for (Execution execution : executions) {
      runtimeService.signalEventReceived("testSignal", execution.getId());
  }
    
     Assert.assertTrue(processInstance.isEnded());
    }
</java>

And here is the definition of the process:

<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="testSignalProcess" name="Test signal process" isExecutable="true">
    <startEvent id="startEvent" name="Start"></startEvent>
    <parallelGateway id="parallelgateway1" name="Parallel Gateway"></parallelGateway>
    <parallelGateway id="parallelgateway2" name="Parallel Gateway"></parallelGateway>
    <endEvent id="endEvent" name="End"></endEvent>
    <callActivity id="testSignalSubProcessCallActivity" name="Test signal sub-process" calledElement="testSignalSubProcess"></callActivity>
    <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="parallelgateway1"></sequenceFlow>
    <sequenceFlow id="flow2" sourceRef="parallelgateway1" targetRef="testSignalSubProcessCallActivity"></sequenceFlow>
    <sequenceFlow id="flow3" sourceRef="testSignalSubProcessCallActivity" targetRef="parallelgateway2"></sequenceFlow>
    <sequenceFlow id="flow4" sourceRef="parallelgateway2" targetRef="endEvent"></sequenceFlow>
    <scriptTask id="testScriptTask" name="Test script task" scriptFormat="javascript" activiti:autoStoreVariables="false">
      <script>load("nashorn:mozilla_compat.js");

importPackage(java.lang);

System.out.println("Test");</script>
    </scriptTask>
    <sequenceFlow id="flow5" sourceRef="parallelgateway1" targetRef="testScriptTask"></sequenceFlow>
    <sequenceFlow id="flow6" sourceRef="testScriptTask" targetRef="parallelgateway2"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_testSignalProcess">
    <bpmndi:BPMNPlane bpmnElement="testSignalProcess" id="BPMNPlane_testSignalProcess">
      <bpmndi:BPMNShape bpmnElement="startEvent" id="BPMNShape_startEvent">
        <omgdc:Bounds height="35.0" width="35.0" x="440.0" y="80.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="parallelgateway1" id="BPMNShape_parallelgateway1">
        <omgdc:Bounds height="40.0" width="40.0" x="437.0" y="170.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="parallelgateway2" id="BPMNShape_parallelgateway2">
        <omgdc:Bounds height="40.0" width="40.0" x="437.0" y="320.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endEvent" id="BPMNShape_endEvent">
        <omgdc:Bounds height="35.0" width="35.0" x="440.0" y="420.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="testSignalSubProcessCallActivity" id="BPMNShape_testSignalSubProcessCallActivity">
        <omgdc:Bounds height="61.0" width="105.0" x="530.0" y="240.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="testScriptTask" id="BPMNShape_testScriptTask">
        <omgdc:Bounds height="55.0" width="105.0" x="405.0" y="243.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="457.0" y="115.0"></omgdi:waypoint>
        <omgdi:waypoint x="457.0" y="170.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="477.0" y="190.0"></omgdi:waypoint>
        <omgdi:waypoint x="582.0" y="190.0"></omgdi:waypoint>
        <omgdi:waypoint x="582.0" y="240.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="582.0" y="301.0"></omgdi:waypoint>
        <omgdi:waypoint x="582.0" y="339.0"></omgdi:waypoint>
        <omgdi:waypoint x="477.0" y="340.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="457.0" y="360.0"></omgdi:waypoint>
        <omgdi:waypoint x="457.0" y="420.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
        <omgdi:waypoint x="457.0" y="210.0"></omgdi:waypoint>
        <omgdi:waypoint x="457.0" y="243.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
        <omgdi:waypoint x="457.0" y="298.0"></omgdi:waypoint>
        <omgdi:waypoint x="457.0" y="320.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>
</code>

And the sub-process that it calls:

<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">
  <signal id="testSignal" name="testSignal"></signal>
  <message id="testMessage" name="testMessage"></message>
  <process id="testSignalSubProcess" name="Test signal sub-process" isExecutable="true">
    <startEvent id="startEvent" name="Start"></startEvent>
    <endEvent id="endEvent" name="End"></endEvent>
    <intermediateCatchEvent id="testEvent" name="Test event">
      <signalEventDefinition signalRef="testSignal"></signalEventDefinition>
    </intermediateCatchEvent>
    <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="testEvent"></sequenceFlow>
    <sequenceFlow id="flow2" sourceRef="testEvent" targetRef="endEvent"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_testSignalSubProcess">
    <bpmndi:BPMNPlane bpmnElement="testSignalSubProcess" id="BPMNPlane_testSignalSubProcess">
      <bpmndi:BPMNShape bpmnElement="startEvent" id="BPMNShape_startEvent">
        <omgdc:Bounds height="35.0" width="35.0" x="490.0" y="90.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endEvent" id="BPMNShape_endEvent">
        <omgdc:Bounds height="35.0" width="35.0" x="490.0" y="290.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="testEvent" id="BPMNShape_testEvent">
        <omgdc:Bounds height="35.0" width="35.0" x="490.0" y="190.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="507.0" y="125.0"></omgdi:waypoint>
        <omgdi:waypoint x="507.0" y="190.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="507.0" y="225.0"></omgdi:waypoint>
        <omgdi:waypoint x="507.0" y="290.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>
</code>

rallen1
Confirmed Champ
Confirmed Champ
Hello rmilovic,

I was able to get what you desired, but it took a bit of modification to your test. It was also a bit klugey as I am not sure how else to go about this given Activiti's design.


1) When a subprocess listens for a signal it appears to spawn a completely separate process with a businessKey = null and parentId = null. As is, if you started multiple "testSignalProcess" instances, I cannot see a way to find the specific subprocess of a specific testSignalProcess using a logical activiti way.
2) To get around this, I put a variable "businessKey" into the variables of the testSignalProcess instance. Then, when calling the subprocess, we specify in the bpmn to pass "businessKey" into the subprocess.
3) We can then query for executions with the variableValueEquals. For example,

<java>
  List<Execution> parentExecutions = runtimeService.createExecutionQuery()
       .variableValueEquals("businessKey", businessKey)
       .list();
</java>

I originally thought this would return all executions, including those that are listening on the signal. It turns out this will only return the "processInstance" execution. Basically, the parent execution. Thus, with these parent executions we can find all child subscribed executions. It is not efficient at all, but it can be done something like this:

<java>
  for (Execution parentExecution: parentExecutions)
  {
   List<Execution> subscribedChildrenExecutions = runtimeService.createExecutionQuery()
     .signalEventSubscriptionName(signalName)
     .processInstanceId(parentExecution.getProcessInstanceId())
     .list();
  
   for (Execution subscribedExecution : subscribedChildrenExecutions)
   {
    LOG.info("Signalling: " + subscribedExecution);
    runtimeService.signalEventReceived(signalName, subscribedExecution.getId());
   }
  }
</java>


The full unit test:

<java>
package org.activiti;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.activiti.engine.HistoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.test.ActivitiRule;
import org.activiti.engine.test.Deployment;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyUnitTest
{
private static final Logger LOG = LoggerFactory.getLogger( MyUnitTest.class );

private static final String BUSINESS_KEY_NAME = "businessKey";
private static final String SIGNAL_NAME = "testSignal";
private static final String PROCESS_DEFINITION_ID = "testSignalProcess";

@Rule
public ActivitiRule activitiRule = new ActivitiRule();

private RuntimeService runtimeService;
private HistoryService historyService;

@Before
public void setUp() throws Exception
{
  runtimeService = activitiRule.getRuntimeService();
  historyService = activitiRule.getHistoryService();
}

// ———–

private void logExecution(Execution execution)
{
  String processInstanceId = execution.getProcessInstanceId();
 
  ProcessInstance process = runtimeService.createProcessInstanceQuery()
    .processInstanceId(processInstanceId)
    .singleResult();
 
  LOG.info(String.format("[%s]: BusinessKey: %s, Parent: %s, ActivitiId: %s, ProcessId: %s, Variables: %s",
    execution.getId(), process.getBusinessKey(), execution.getParentId(),
    execution.getActivityId(), execution.getProcessInstanceId(),
    runtimeService.getVariables(execution.getId())
    )
  );
 
  LOG.info(String.format("   [%s]: ParentId: %s, BusinessKey:%s, ActivitiId:%s, ",
    process.getProcessInstanceId(), process.getParentId(), process.getBusinessKey(),
    process.getActivityId()
    )
  );
}

private void logExecutions(List<Execution> executions)
{
  for (Execution execution : executions) {
      logExecution(execution);
  }
  LOG.info("————");
}


private void signalAllWithBusinessKey(String signalName, String businessKey) throws Exception
{
  List<Execution> parentExecutions = runtimeService.createExecutionQuery()
       .variableValueEquals(BUSINESS_KEY_NAME, businessKey)
       .list();
 
  logExecutions(parentExecutions);
 
  for (Execution parentExecution: parentExecutions)
  {
   List<Execution> subscribedChildrenExecutions = runtimeService.createExecutionQuery()
     .signalEventSubscriptionName(signalName)
     .processInstanceId(parentExecution.getProcessInstanceId())
     .list();
  
   for (Execution subscribedExecution : subscribedChildrenExecutions)
   {
    LOG.info("Signalling: " + subscribedExecution);
    runtimeService.signalEventReceived(signalName, subscribedExecution.getId());
   }
  }
}

// ———–

@Test
@Deployment(resources = {"org/activiti/test/parent.bpmn20.xml", "org/activiti/test/child.bpmn20.xml"})
public void test_signalSubscription_Working() throws Exception {
  String businessKey = UUID.randomUUID().toString();
  Map<String, Object> variables = new HashMap<String, Object>();
  variables.put(BUSINESS_KEY_NAME, businessKey);
 
  ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(PROCESS_DEFINITION_ID, businessKey, variables);
 
  Thread.sleep(100);
 
     signalAllWithBusinessKey(SIGNAL_NAME, businessKey);
    
     Thread.sleep(100);
    
     HistoricProcessInstance processHistory = historyService.createHistoricProcessInstanceQuery()
      .processInstanceId(processInstance.getProcessInstanceId())
      .singleResult();
    
     Assert.assertNotNull(processHistory.getEndTime());
}

@Test
@Deployment(resources = {"org/activiti/test/other.bpmn20.xml", "org/activiti/test/child.bpmn20.xml"})
@Ignore("This does not work, but helped me debug what is going on.")
public void test_signalSubscription_Logical() throws Exception {
  String businessKey = UUID.randomUUID().toString();
  Map<String, Object> variables = new HashMap<String, Object>();
  variables.put(BUSINESS_KEY_NAME, businessKey);
 
  ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(PROCESS_DEFINITION_ID, businessKey, variables);
 
     List<Execution> allExecutions = runtimeService.createExecutionQuery()
       .processInstanceBusinessKey(businessKey, true)
       .list();

     LOG.info("Process Instance and Child Executions: " + allExecutions);
     logExecutions(allExecutions);
    
    
    
     List<Execution> variableExecutions = runtimeService.createExecutionQuery()
       .variableValueEquals(BUSINESS_KEY_NAME, businessKey)
       .list();

     LOG.info("Executions with Variable Matching: " + variableExecutions);
     logExecutions(variableExecutions);
    
    
     List<Execution> subscribedExecutions = runtimeService.createExecutionQuery()
       .signalEventSubscriptionName(SIGNAL_NAME)
       .list();
    
     LOG.info("Subscribed Executions: " + subscribedExecutions);
     logExecutions(subscribedExecutions);
    
    
     List<Execution> executions = runtimeService.createExecutionQuery()
       // TODO: Activiti seems to spawn a completely separate process just to listen for the signal,
       // so searching on processInstanceBusinessKey does not seem to work.
       //
       //.processInstanceBusinessKey(businessKey, true)
       //
       // Search on variable instead. This seems like it should work as all the children share the variables, but
       // this will only return the parents for some reason.
       .variableValueEquals(BUSINESS_KEY_NAME, businessKey)
       .signalEventSubscriptionName(SIGNAL_NAME)
       .list();
    
     LOG.info("Subscribed and Children Executions: " + executions);
     logExecutions(executions);
    
     for (Execution execution : executions) {
      runtimeService.signalEventReceived(SIGNAL_NAME, execution.getId());
  }

     HistoricProcessInstance processHistory = historyService.createHistoricProcessInstanceQuery()
          .processInstanceId(processInstance.getProcessInstanceId())
          .singleResult();
    
     Assert.assertNotNull(processHistory.getEndTime());
}
}
</java>

I also changed the parent bpmn to listen on a signal and pass in the businessKey to the subprocess (parent.bpmn):

<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">
  <signal id="testSignal" name="testSignal"></signal>
  <process id="testSignalProcess" name="Test signal process" isExecutable="true">
    <startEvent id="startEvent" name="Start"></startEvent>
    <parallelGateway id="parallelgateway1" name="Parallel Gateway"></parallelGateway>
    <parallelGateway id="parallelgateway2" name="Parallel Gateway"></parallelGateway>
    <endEvent id="endEvent" name="End"></endEvent>
    <callActivity id="testSignalSubProcessCallActivity" name="Test signal sub-process" calledElement="testSignalSubProcess">
      <extensionElements>
        <activiti:in source="businessKey" target="businessKey"></activiti:in>
      </extensionElements>
    </callActivity>
    <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="parallelgateway1"></sequenceFlow>
    <sequenceFlow id="flow2" sourceRef="parallelgateway1" targetRef="testSignalSubProcessCallActivity"></sequenceFlow>
    <sequenceFlow id="flow3" sourceRef="testSignalSubProcessCallActivity" targetRef="parallelgateway2"></sequenceFlow>
    <sequenceFlow id="flow4" sourceRef="parallelgateway2" targetRef="endEvent"></sequenceFlow>
    <scriptTask id="testScriptTask" name="Test script task" scriptFormat="javascript" activiti:autoStoreVariables="false">
      <script>importPackage(java.lang);

System.out.println("Test");</script>
    </scriptTask>
    <sequenceFlow id="flow5" sourceRef="parallelgateway1" targetRef="testScriptTask"></sequenceFlow>
    <sequenceFlow id="flow6" sourceRef="testScriptTask" targetRef="parallelgateway2"></sequenceFlow>
    <intermediateCatchEvent id="signalintermediatecatchevent1" name="SignalCatchEvent">
      <signalEventDefinition signalRef="testSignal"></signalEventDefinition>
    </intermediateCatchEvent>
    <sequenceFlow id="flow7" sourceRef="parallelgateway1" targetRef="signalintermediatecatchevent1"></sequenceFlow>
    <sequenceFlow id="flow8" sourceRef="signalintermediatecatchevent1" targetRef="parallelgateway2"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_testSignalProcess">
    <bpmndi:BPMNPlane bpmnElement="testSignalProcess" id="BPMNPlane_testSignalProcess">
      <bpmndi:BPMNShape bpmnElement="startEvent" id="BPMNShape_startEvent">
        <omgdc:Bounds height="35.0" width="35.0" x="440.0" y="80.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="parallelgateway1" id="BPMNShape_parallelgateway1">
        <omgdc:Bounds height="40.0" width="40.0" x="437.0" y="170.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="parallelgateway2" id="BPMNShape_parallelgateway2">
        <omgdc:Bounds height="40.0" width="40.0" x="437.0" y="320.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endEvent" id="BPMNShape_endEvent">
        <omgdc:Bounds height="35.0" width="35.0" x="440.0" y="420.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="testSignalSubProcessCallActivity" id="BPMNShape_testSignalSubProcessCallActivity">
        <omgdc:Bounds height="61.0" width="105.0" x="530.0" y="240.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="testScriptTask" id="BPMNShape_testScriptTask">
        <omgdc:Bounds height="55.0" width="105.0" x="405.0" y="243.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="signalintermediatecatchevent1" id="BPMNShape_signalintermediatecatchevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="340.0" y="253.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="457.0" y="115.0"></omgdi:waypoint>
        <omgdi:waypoint x="457.0" y="170.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="477.0" y="190.0"></omgdi:waypoint>
        <omgdi:waypoint x="582.0" y="190.0"></omgdi:waypoint>
        <omgdi:waypoint x="582.0" y="240.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="582.0" y="301.0"></omgdi:waypoint>
        <omgdi:waypoint x="582.0" y="339.0"></omgdi:waypoint>
        <omgdi:waypoint x="477.0" y="340.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="457.0" y="360.0"></omgdi:waypoint>
        <omgdi:waypoint x="457.0" y="420.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
        <omgdi:waypoint x="457.0" y="210.0"></omgdi:waypoint>
        <omgdi:waypoint x="457.0" y="243.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
        <omgdi:waypoint x="457.0" y="298.0"></omgdi:waypoint>
        <omgdi:waypoint x="457.0" y="320.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
        <omgdi:waypoint x="437.0" y="190.0"></omgdi:waypoint>
        <omgdi:waypoint x="357.0" y="190.0"></omgdi:waypoint>
        <omgdi:waypoint x="357.0" y="253.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
        <omgdi:waypoint x="357.0" y="288.0"></omgdi:waypoint>
        <omgdi:waypoint x="357.0" y="339.0"></omgdi:waypoint>
        <omgdi:waypoint x="437.0" y="340.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>
</code>


I hope this helps. As a side note, I think this has helped me with my Activiti problem (so thank you for asking the question).



P.S.: ProcessInstance.isEnded() does not seem to work. Using the history service seems to though.
P.P.S: I highly recommend changing the logging settings. In src/test/resouces/log4j.properties: set "log4j.rootLogger=DEBUG, CA" to "log4j.rootLogger=INFO, CA"

jbarrez
Star Contributor
Star Contributor
@rallen: wow. What a response. Impressive!

rmilovic
Champ in-the-making
Champ in-the-making
Thank you rallen. Your answer is great. I wish all answers are this detailed. I did my implementation a little different based on your answer.
Basically I'm mapping the process instance id from the master process to a child process (call action) as an input parameter (masterProcessInstanceId = ${execution.getProcessInstanceId()}). This parameter can then be passed down the hierarchy of child processes. Then, I'm using the following code to signal master execution and all of its sub-executions:

<java>
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testSignalProcess");

// send event to child executions
List<Execution> childExecutions = runtimeService.createExecutionQuery()
        .processVariableValueEquals("masterProcessInstanceId", processInstance.getProcessInstanceId())
        .signalEventSubscriptionName("testSignal").list();
    
for (Execution childExecution : childExecutions) {
        runtimeService.signalEventReceived("testSignal", childExecution.getId());
}

// send event to master execution
List<Execution> executions = runtimeService.createExecutionQuery()
        .processInstanceId(processInstance.getProcessInstanceId())
     .signalEventSubscriptionName("testSignal").list();
    
for (Execution execution : executions) {
        runtimeService.signalEventReceived("testSignal", execution.getId());
}
</java>

rmilovic
Champ in-the-making
Champ in-the-making
Question for Activiti development team: Can you consider adding the option to propagate the signal event to all children of a given execution. It could look something like this:

<java>
void signalEventReceived(String signalName, String executionId, boolean propagate);
</java>

One more question. In both rallen's and my test example processInstance.isEnded() returns false although the process instance is finished. Is this expected behavior? If it is, than why is behaving like that? Just asking because it is confusing.

I can't speak for the dev stuff, but I agree in that it would be nice have some functions that dealt with children. In regards to the isEnded(),  I think the processInstance.isEnded() may have to do with processInstance not being up-to-date with the database.

In the code:
<java>
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess");
</java>

The method runtimeService.startProcessInstanceByKey() and its similar siblings will only return if the process has finished or hit a blocking state, such as waiting for a message or signal. I am not 100% sure, but it may also return if it hits task/activity/subprocess that has "Asynchronous" checked (activiti:async="true").

I guess the safest way to check is to use the HistoryService. It might be more intuitive if the HistoryService were used in the default unit test. It would mean more code though and a dependency on the history service.

jbarrez
Star Contributor
Star Contributor
>  In both rallen's and my test example processInstance.isEnded() returns false although the process instance is finished. Is this expected behavior?

rallen is correct. The process instance is stale at that point and doesnt reflect the latest state of things.

> Can you consider adding the option to propagate the signal event to all children of a given execution.

I'm not sure about the use case. The signal event, as per the spec, is caught by a boundary event (or intermediate) that is waiting for it. Propagating it would be confusing.

But I'm not yet following your use case. Could you elaborate on the business use case for it?

rmilovic
Champ in-the-making
Champ in-the-making
> rallen is correct. The process instance is stale at that point and doesn't reflect the latest state of things.

I agree, but the method name is confusing and made me think it will work this way. I'm not familiar with the internals of the framework, but dummy search for references on method end() of the ExecutionEntity type found two places where it is used: TerminateEndEventActivityBehavior#execute and NoneEndEventActivityBehavior#execute. Again, I may be wrong, but shouldn't this set associated process instance ended value to true when end activity is reached inside a process?

> I'm not sure about the use case. The signal event, as per the spec, is caught by a boundary event (or intermediate) that is waiting for it. Propagating it would be confusing. But I'm not yet following your use case. Could you elaborate on the business use case for it?

In case when you have a complex hierarchy of the process definitions. In those cases it is more appropriate to use CallActivity instead of a SubProcess element in order to make process definitions smaller and easier to read. It also allows you to reuse them as part of the other processes or run them independently. In my particular situation I'm catching the same signal in multiple child processes and react on them. In order to do it, I need to get the execution id of every process instance of that hierarchy which is not a simple task. Passing some unique identifier (like business key or master process instance id) as a variable can be a solution, but it requires some manual intervention. I'm fine with this solution, but I was just wondering could it be little more elegant.