cancel
Showing results for 
Search instead for 
Did you mean: 

Using Error Events as general shortcutting solution

ekolesnikov
Champ in-the-making
Champ in-the-making
Hi guys,

Been a while since I last visited forums.

We have quite interesting approval process - the "manager" does not just approves or rejects, but also have an option to "escalate" the request (business case being "I have no objections, but I'm not comfortable authorising this on my own") to one or two nominated approvers.
In addition to that, if both approvers are nominated, the approvals have to go through in particular order (A -> B, not the other way around).

At the end of the whole thing, the "Your request has been approved" or "Your request has been rejected" email generation routine is triggered by the Service task.

My problem is - there are way too many references to process completion and it kind of messes up the whole BPMN diagram.

I.e. the "rejected" path is triggered when a "manager" rejects, when approver "A" rejects, or when approver "B" rejects. The "Approved" path is triggered when a "manager" approves, or approves and escalates to "A" or "B" alone and they approve, or to both "A" and "B" together and they both approve. Additionally, after "A" approves, there is another decision making path testing if "B" approval is also required.

Just imagine how messed up the whole thing is when drawn straight away. So my idea was to simplify the diagram by externalising "Approved" and "Rejected" paths from the main process and calling them when needed.

My issue here is that the only appropriate throw-catch solution that only operates within the scope of the current execution is an Error events and event sub-processes.

Frankly, using "Error" events to follow the "successful" path does not look appropriate at all - I'd say the "Signal" event is the right one, but the problem is that it triggers ALL process definitions out there which is definitely not something I desire.

Here goes the question - is it even a proper use case for the "Error" event? If not, is there anything more appropriate for my case?

Many thanks in advance.
15 REPLIES 15

martin_grofcik
Confirmed Champ
Confirmed Champ
Hi,

Is it possible to use messages? (http://www.activiti.org/userguide/#bpmnMessageEventDefinition)
Unlike a signal, a message event is always directed at a single receiver.

Regards
Martin

ekolesnikov
Champ in-the-making
Champ in-the-making
Hi Martin,

Thanks for the swift reply - I'm actually looking at the messages right now. I've used Error events to kick off "Your application has been rejected" event subprocess, which kind of makes sense to me.

My problem with messages is similar to signals - they are definition-specific, rather than instance-specific. I.e. if there are two process instances in the same state and one of them throws an error, I'm sure that only relevant process will be affected. However, I understand that it is possible to overcome this by using service tasks.

From where I am, it looks like the service task is going to be a one-liner:

public void execute(DelegateExecution execution) throws Exception {
    execution.getEngineServices().getRuntimeService().messageEventReceived("approval-task-assigned", execution.getId());
}

In my case, the "approval-task-assigned" kicks off the message-activated subprocess that will send an email to the assignee, an email to the initiator and also start the nagging cycle to annoy the heck out of assignee every 7 days. That would mean that sub-process has to have access to all process variables and also be asynchronous. Is it necessary to make the service task async, or messaging is itself asynchronous?

Many thanks.

ekolesnikov
Champ in-the-making
Champ in-the-making
Ok, so I "kind of" have a solution for that. Frankly, I hate its guts, I believe what I did was wrong and there should be a better way of doing something as simple as this.

What I was trying to achieve was reusable sub-routine that could be called from various steps within the process, with the following capabilities:

1. It should have full access to process instance variables;
2. Event handling should be contained within current process instance.

Using "Call Activity" node failed (1) - it could not access anything from parent process.
Using "Normal" signal start event failed both (1) and (2)
Using event sub-process with message start event (http://www.activiti.org/userguide/#bpmnEventSubprocess : "Activiti only supports Event Sub-Process triggered using an Error Start Event or Message Start Event") did not work for me at all, I was unable to start sub-process by sending message event.

"Real" business case behind all that is described in the OP post: I need "Your request has progressed", "You have a new request, please approve" and "You still have a request to approve, please do it right now!" trio to get started each time process arrives to one of the "approval" or "escalation approval" stages. Implementing it directly will make the whole process look like a bowl of fettuccine (it's already quite complicated, just with its bare functionality) and I would like to avoid it at all costs.

The process definition looks like this: http://www.screencast.com/t/R0IorQnS96D

<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/processdef">
  <signal id="approval-task-assigned" name="approval-task-assigned"></signal>
  <process id="tester" isExecutable="true">
    <startEvent id="sid-DAA05B35-BF2C-4037-9DB6-4E600A5BDD49"></startEvent>
    <sequenceFlow id="sid-C05B750B-2E48-4A9E-9F38-8D80D232F64A" sourceRef="sid-DAA05B35-BF2C-4037-9DB6-4E600A5BDD49" targetRef="sid-E7959CD3-49FB-455F-B1FB-EF02237B3143"></sequenceFlow>
    <scriptTask id="sid-95C593A2-189C-4650-BAAC-86860CC0E33C" name="Print stuff" scriptFormat="groovy" activiti:autoStoreVariables="false">
      <script>System.err.println(myname + " is soooo cool!")</script>
    </scriptTask>
    <endEvent id="sid-8AC46B0F-7600-4075-AB7B-1FD449EAB0B2"></endEvent>
    <sequenceFlow id="sid-3DFC62F0-52E8-4DF9-9080-227CE4A80D26" sourceRef="sid-8490F6D3-35B2-477D-9C91-498AF1D6027B" targetRef="sid-95C593A2-189C-4650-BAAC-86860CC0E33C"></sequenceFlow>
    <scriptTask id="sid-4DC4085B-D0F9-48EE-9014-BA4E8DA9F37F" name="Set up variable" scriptFormat="groovy" activiti:autoStoreVariables="false">
      <script>execution.setVariable("myname", "scripto");</script>
    </scriptTask>
    <sequenceFlow id="sid-21531192-0351-412A-BCF9-6DB586F625F6" sourceRef="sid-95C593A2-189C-4650-BAAC-86860CC0E33C" targetRef="sid-8AC46B0F-7600-4075-AB7B-1FD449EAB0B2"></sequenceFlow>
    <serviceTask id="sid-8490F6D3-35B2-477D-9C91-498AF1D6027B" name="Kick off subprocess" activiti:class="com.servian.activiti.service.message.MessageDelegate"></serviceTask>
    <sequenceFlow id="sid-AF71E44A-3585-4077-A761-5C7D59DEF6BA" sourceRef="sid-4DC4085B-D0F9-48EE-9014-BA4E8DA9F37F" targetRef="sid-8490F6D3-35B2-477D-9C91-498AF1D6027B"></sequenceFlow>
    <scriptTask id="sid-DE4D72FE-0EF6-445F-9305-3454F12B6504" name="Print stuff" scriptFormat="groovy" activiti:autoStoreVariables="false">
      <script>System.err.println(myname + " is available in subprocess!")</script>
    </scriptTask>
    <sequenceFlow id="sid-3AA8A9EB-DA8E-48CB-A8D5-2158E273F9CC" sourceRef="sid-F2599F55-5FB2-431E-8303-E1A60D9866B5" targetRef="sid-DE4D72FE-0EF6-445F-9305-3454F12B6504"></sequenceFlow>
    <intermediateCatchEvent id="sid-B7623845-A147-4949-91D5-68E7F741930C">
      <signalEventDefinition signalRef="approval-task-assigned"></signalEventDefinition>
    </intermediateCatchEvent>
    <parallelGateway id="sid-E7959CD3-49FB-455F-B1FB-EF02237B3143"></parallelGateway>
    <sequenceFlow id="sid-9A88BB2E-6FC0-40B1-85F4-F01A1A798C68" sourceRef="sid-E7959CD3-49FB-455F-B1FB-EF02237B3143" targetRef="sid-B7623845-A147-4949-91D5-68E7F741930C"></sequenceFlow>
    <parallelGateway id="sid-F2599F55-5FB2-431E-8303-E1A60D9866B5"></parallelGateway>
    <sequenceFlow id="sid-8DFFF4E7-5C95-4473-AB72-85E70C8238EA" sourceRef="sid-B7623845-A147-4949-91D5-68E7F741930C" targetRef="sid-F2599F55-5FB2-431E-8303-E1A60D9866B5"></sequenceFlow>
    <intermediateCatchEvent id="sid-23B344B0-B7BB-4745-A2BC-1A043802748C">
      <timerEventDefinition>
        <timeDuration>PT2S</timeDuration>
      </timerEventDefinition>
    </intermediateCatchEvent>
    <sequenceFlow id="sid-4E1058B3-901C-48E8-8779-861B85394E09" sourceRef="sid-23B344B0-B7BB-4745-A2BC-1A043802748C" targetRef="sid-4DC4085B-D0F9-48EE-9014-BA4E8DA9F37F"></sequenceFlow>
    <sequenceFlow id="sid-30F1215F-DB65-448B-A879-4C70B77880B5" sourceRef="sid-E7959CD3-49FB-455F-B1FB-EF02237B3143" targetRef="sid-23B344B0-B7BB-4745-A2BC-1A043802748C"></sequenceFlow>
    <sequenceFlow id="sid-0A925249-B329-4AB6-B512-7CDFBA8A56EA" sourceRef="sid-F2599F55-5FB2-431E-8303-E1A60D9866B5" targetRef="sid-B7623845-A147-4949-91D5-68E7F741930C"></sequenceFlow>
    <endEvent id="sid-45723BBA-1078-4479-ACE3-5EE15D9ED834"></endEvent>
    <sequenceFlow id="sid-71088E0C-E6C9-4121-9501-AEBB29913270" sourceRef="sid-DE4D72FE-0EF6-445F-9305-3454F12B6504" targetRef="sid-45723BBA-1078-4479-ACE3-5EE15D9ED834"></sequenceFlow>
  </process>
</definitions>
</code>

martin_grofcik
Confirmed Champ
Confirmed Champ
Hi Egor,

Using "Call Activity" node failed (1) - it could not access anything from parent process.

If this is the only issue with call activity, you can create your own org.activiti.engine.impl.el.ExpressionManager and ELResolver to get variables from the process hierarchy.

Regards
Martin

If this is the only issue with call activity, you can create your own org.activiti.engine.impl.el.ExpressionManager and ELResolver to get variables from the process hierarchy.

Hi Martin,

The problem with process hierarchy in Call Activity is that there is no process hierarchy. Calling process is not available via execution.getParentId(); neither it is available through execution.getProcessInstanceId(); which points to sub-process instance ID rather than caller process instance ID.

There is, however, getSuperExecution()  method declared on InterpretableExecution interface implemented by Execution and it does point to proper processInstanceId of calling process. However, since it is a part of org.activiti.engine.impl.pvm.runtime package, I'd assume it to be internal, private, undocumented, implementation-specific and prone-to-change-without-notice. Therefore, any client code relying on it would be categorised as a "hack" in my book.

All of that is as of Activiti 5.15.1.

trademak
Star Contributor
Star Contributor
You can use the ProcessInstanceQuery to retrieve the parent process of a specific sub process using the subProcessInstanceId method.

Best regards,

ekolesnikov
Champ in-the-making
Champ in-the-making
You can use the ProcessInstanceQuery to retrieve the parent process of a specific sub process using the subProcessInstanceId method.

"There is no end to it…"
OK, so I am able to resolve parent process from the sub, but there's another issue: there seems to be no way to resolve expressions against parent process:

<code>
private Object resolveVar(Expression expr, DelegateExecution execution, ProcessInstance parent) {
  Object result = null;
  try {
   //first attempt to resolve on local scope
   result = expr.getValue(execution);  
  } catch(Exception ex) {
   //if we're in subprocess, check the parent instance.
   if(parent != null) {
    //ERROR - ProcessInstance does not implement VariableScope!
    result = expr.getValue(parent);
   } else {
    throw new ActivitiException("Unable to resolve property and there is no parent scope.", ex);
   }
  }
  return result;
 
}
</code>

I'll be working around this issue today - but anyways, any ideas how to overcome this?

For now, I simply "fixed" the offending line by casting ProcessInstance into VariableScope: <code>result = expr.getValue((VariableScope)parent);</code>, but this is basically implementation-specific workaround which will break as soon as you guys decide to change ProcessInstance -> ExecutionEntity <== VariableScopeImpl hierarchy in any future versions.

trademak
Star Contributor
Star Contributor
It's really difficult to provide help when you paste a snippet of code without any context. What are you trying to do here? Do you want to get a specific variable value from the parent process instance? Why are you not using the RuntimService getVariable method for that?

Best regards,

ekolesnikov
Champ in-the-making
Champ in-the-making
Hi Tijs,

Apologies for being ambiguous. What I was trying to do was this:

OK, so I am able to resolve parent process from the sub, but there's another issue: there seems to be no way to resolve expressions against parent process.

What I have now is "request" email and "nagging cycle" of emails running in a sub-process using Service tasks. Some of the tasks need access to parent process variables via expressions (i.e. ${approverFullName} in sub-process with approverFullName defined in parent).

What the code snippet above was all about is that the resolver first tries to resolve the expression in the current execution scope (which works fine because it's represented by the DelegateExpression which implements VariableScope). If it fails, the parent scope is consulted - now that's where I have a problem, because ProcessInstance is not VariableScope.