Skip to content

Commit

Permalink
JBPM-5363 - Evaluation of variable expressions in subprocesses (#633)
Browse files Browse the repository at this point in the history
  • Loading branch information
mswiderski committed Oct 17, 2016
1 parent eef6b0c commit a2893d3
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 44 deletions.
18 changes: 18 additions & 0 deletions jbpm-bpmn2/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java
Expand Up @@ -2669,4 +2669,22 @@ public void testEventSubprocessWithEmbeddedSignals() throws Exception {

assertProcessInstanceFinished(processInstance, ksession);
}

@Test
public void testEventSubprocessWithExpression() throws Exception {
KieBase kbase = createKnowledgeBase("BPMN2-EventSubprocessSignalExpression.bpmn2");
ksession = createKnowledgeSession(kbase);

Map<String, Object> params = new HashMap<>();
params.put("x", "signalling");
ProcessInstance processInstance = ksession.startProcess("BPMN2-EventSubprocessSignalExpression", params);

assertProcessInstanceActive(processInstance.getId(), ksession);
assertProcessInstanceActive(processInstance);
ksession = restoreSession(ksession, true);

ksession.signalEvent("signalling", null, processInstance.getId());

assertProcessInstanceFinished(processInstance, ksession);
}
}
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.jboss.org/drools" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="Definitions_1" expressionLanguage="http://www.mvel.org/2.0" targetNamespace="http://www.jboss.org/drools" typeLanguage="http://www.java.com/javaTypes">
<bpmn2:signal id="MySignal" name="#{x}"/>
<bpmn2:itemDefinition id="_xItem" structureRef="String" />
<bpmn2:process id="BPMN2-EventSubprocessSignalExpression" tns:version="1" tns:packageName="defaultPackage" name="Default Process">
<bpmn2:property id="x" itemSubjectRef="_xItem"/>
<bpmn2:startEvent id="StartEvent_1" name="start">
<bpmn2:outgoing>SequenceFlow_2</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:sequenceFlow id="SequenceFlow_1" tns:priority="1" sourceRef="UserTask_1" targetRef="EndEvent_1"/>
<bpmn2:endEvent id="EndEvent_1" name="end">
<bpmn2:incoming>SequenceFlow_1</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:userTask id="UserTask_1" name="User Task 1">
<bpmn2:incoming>SequenceFlow_2</bpmn2:incoming>
<bpmn2:outgoing>SequenceFlow_1</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="SequenceFlow_2" tns:priority="1" name="" sourceRef="StartEvent_1" targetRef="UserTask_1"/>
<bpmn2:subProcess id="SubProcess_1" name="Sub Process 1" triggeredByEvent="true" >
<bpmn2:startEvent id="StartEvent_2" name="start-sub" isInterrupting="true">
<bpmn2:outgoing>SequenceFlow_3</bpmn2:outgoing>
<bpmn2:signalEventDefinition id="SignalEventDefinition_1" signalRef="MySignal"/>
</bpmn2:startEvent>
<bpmn2:scriptTask id="ScriptTask_1" name="sub-script">
<bpmn2:incoming>SequenceFlow_3</bpmn2:incoming>
<bpmn2:outgoing>SequenceFlow_4</bpmn2:outgoing>
<bpmn2:script>System.out.println("Event sub process triggered");</bpmn2:script>
</bpmn2:scriptTask>
<bpmn2:sequenceFlow id="SequenceFlow_3" tns:priority="1" sourceRef="StartEvent_2" targetRef="ScriptTask_1"/>
<bpmn2:endEvent id="EndEvent_2" name="end-sub">
<bpmn2:incoming>SequenceFlow_4</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="SequenceFlow_4" tns:priority="1" sourceRef="ScriptTask_1" targetRef="EndEvent_2"/>
</bpmn2:subProcess>
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1" name="Default Process Diagram">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="BPMN2-EventSubprocessSignalExpression">
<bpmndi:BPMNShape id="BPMNShape_1" bpmnElement="StartEvent_1">
<dc:Bounds height="36.0" width="36.0" x="100.0" y="100.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_2" bpmnElement="EndEvent_1">
<dc:Bounds height="36.0" width="36.0" x="500.0" y="100.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_1" bpmnElement="SequenceFlow_1" sourceElement="BPMNShape_UserTask_1" targetElement="BPMNShape_2">
<di:waypoint xsi:type="dc:Point" x="380.0" y="118.0"/>
<di:waypoint xsi:type="dc:Point" x="500.0" y="118.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BPMNShape_UserTask_1" bpmnElement="UserTask_1">
<dc:Bounds height="50.0" width="110.0" x="270.0" y="93.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_2" bpmnElement="SequenceFlow_2" sourceElement="BPMNShape_1" targetElement="BPMNShape_UserTask_1">
<di:waypoint xsi:type="dc:Point" x="136.0" y="118.0"/>
<di:waypoint xsi:type="dc:Point" x="270.0" y="118.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BPMNShape_SubProcess_1" bpmnElement="SubProcess_1" isExpanded="true">
<dc:Bounds height="131.0" width="381.0" x="180.0" y="240.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_StartEvent_1" bpmnElement="StartEvent_2">
<dc:Bounds height="36.0" width="36.0" x="232.0" y="287.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_ScriptTask_1" bpmnElement="ScriptTask_1">
<dc:Bounds height="50.0" width="110.0" x="318.0" y="280.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_3" bpmnElement="SequenceFlow_3" sourceElement="BPMNShape_StartEvent_1" targetElement="BPMNShape_ScriptTask_1">
<di:waypoint xsi:type="dc:Point" x="268.0" y="305.0"/>
<di:waypoint xsi:type="dc:Point" x="318.0" y="305.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BPMNShape_EndEvent_1" bpmnElement="EndEvent_2">
<dc:Bounds height="36.0" width="36.0" x="478.0" y="287.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_4" bpmnElement="SequenceFlow_4" sourceElement="BPMNShape_ScriptTask_1" targetElement="BPMNShape_EndEvent_1">
<di:waypoint xsi:type="dc:Point" x="428.0" y="305.0"/>
<di:waypoint xsi:type="dc:Point" x="478.0" y="305.0"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>
Expand Up @@ -16,8 +16,12 @@

package org.jbpm.process.core.event;

import java.util.function.Function;

public interface EventFilter {

boolean acceptsEvent(String type, Object event);

boolean acceptsEvent(String type, Object event, Function<String, String> resolver);

}
Expand Up @@ -17,6 +17,7 @@
package org.jbpm.process.core.event;

import java.io.Serializable;
import java.util.function.Function;

public class EventTypeFilter implements EventFilter, Serializable {

Expand All @@ -42,4 +43,12 @@ public boolean acceptsEvent(String type, Object event) {
public String toString() {
return "Event filter: [" + this.type + "]";
}

@Override
public boolean acceptsEvent(String type, Object event, Function<String, String> resolver) {
if (this.type != null && resolver.apply(this.type).equals(type)) {
return true;
}
return false;
}
}
Expand Up @@ -16,8 +16,14 @@

package org.jbpm.workflow.core.node;

import java.util.function.Function;

public interface EventNodeInterface {

boolean acceptsEvent(String type, Object event);

default boolean acceptsEvent(String type, Object event, Function<String, String> resolver) {
return acceptsEvent(type, event);
}

}
Expand Up @@ -17,6 +17,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

import org.jbpm.process.core.event.EventTypeFilter;
import org.jbpm.process.core.timer.Timer;
Expand Down Expand Up @@ -76,6 +77,20 @@ public boolean acceptsEvent(String type, Object event) {
}
return super.acceptsEvent(type, event);
}

@Override
public boolean acceptsEvent(String type, Object event, Function<String, String> resolver) {
if (resolver == null) {
return acceptsEvent(type, event);
}

for( EventTypeFilter filter : this.eventTypeFilters ) {
if( filter.acceptsEvent(type, event, resolver) ) {
return true;
}
}
return super.acceptsEvent(type, event);
}

}

Expand Up @@ -27,8 +27,11 @@
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

import org.drools.core.common.InternalKnowledgeRuntime;
import org.drools.core.util.MVELSafeHelper;
import org.jbpm.process.core.ContextContainer;
import org.jbpm.process.core.context.variable.VariableScope;
import org.jbpm.process.core.timer.Timer;
Expand Down Expand Up @@ -502,12 +505,12 @@ public void signalEvent(String type, Object event) {
}
for (Node node : getWorkflowProcess().getNodes()) {
if (node instanceof EventNodeInterface) {
if (((EventNodeInterface) node).acceptsEvent(type, event)) {
if (((EventNodeInterface) node).acceptsEvent(type, event, (e) -> resolveVariable(e) )) {
if (node instanceof EventNode && ((EventNode) node).getFrom() == null) {
EventNodeInstance eventNodeInstance = (EventNodeInstance) getNodeInstance(node);
eventNodeInstance.signalEvent(type, event);
} else {
if (node instanceof EventSubProcessNode && (((EventSubProcessNode) node).getEvents().contains(type))) {
if (node instanceof EventSubProcessNode && ((resolveVariables(((EventSubProcessNode) node).getEvents()).contains(type)))) {
EventSubProcessNodeInstance eventNodeInstance = (EventSubProcessNodeInstance) getNodeInstance(node);
eventNodeInstance.signalEvent(type, event);
} else {
Expand Down Expand Up @@ -549,6 +552,38 @@ public void signalEvent(String type, Object event) {
}
}

protected List<String> resolveVariables(List<String> events) {
return events.stream().map( event -> resolveVariable(event)).collect(Collectors.toList());
}

private String resolveVariable(String s) {
Map<String, String> replacements = new HashMap<String, String>();
Matcher matcher = PARAMETER_MATCHER.matcher(s);
while (matcher.find()) {
String paramName = matcher.group(1);
if (replacements.get(paramName) == null) {

Object variableValue = getVariable(paramName);
if (variableValue != null) {
String variableValueString = variableValue == null ? "" : variableValue.toString();
replacements.put(paramName, variableValueString);
} else {
try {
variableValue = MVELSafeHelper.getEvaluator().eval(paramName, new ProcessInstanceResolverFactory(this));
String variableValueString = variableValue == null ? "" : variableValue.toString();
replacements.put(paramName, variableValueString);
} catch (Throwable t) {
logger.error("Could not find variable scope for variable {}", paramName);
}
}
}
}
for (Map.Entry<String, String> replacement: replacements.entrySet()) {
s = s.replace("#{" + replacement.getKey() + "}", replacement.getValue());
}
return s;
}

public void addEventListener(String type, EventListener listener, boolean external) {
Map<String, List<EventListener>> eventListeners = external ? this.externalEventListeners : this.eventListeners;
List<EventListener> listeners = eventListeners.get(type);
Expand Down
Expand Up @@ -15,6 +15,9 @@
*/
package org.jbpm.workflow.instance.node;

import java.util.List;
import java.util.stream.Collectors;

import org.jbpm.process.instance.ProcessInstance;
import org.jbpm.workflow.core.node.EventSubProcessNode;
import org.jbpm.workflow.core.node.StartNode;
Expand Down Expand Up @@ -49,7 +52,7 @@ public void internalTrigger(NodeInstance from, String type) {
public void signalEvent(String type, Object event) {
if (getNodeInstanceContainer().getNodeInstances().contains(this) || type.startsWith("Error-") || type.equals("timerTriggered") ) {
StartNode startNode = getCompositeNode().findStartNode();
if (((EventSubProcessNode) getEventBasedNode()).getEvents().contains(type) || type.equals("timerTriggered")) {
if (resolveVariables(((EventSubProcessNode) getEventBasedNode()).getEvents()).contains(type) || type.equals("timerTriggered")) {
NodeInstance nodeInstance = getNodeInstance(startNode);
((StartNodeInstance) nodeInstance).signalEvent(type, event);
}
Expand Down Expand Up @@ -83,5 +86,7 @@ public void nodeInstanceCompleted(org.jbpm.workflow.instance.NodeInstance nodeIn
}
}


protected List<String> resolveVariables(List<String> events) {
return events.stream().map( event -> resolveVariable(event)).collect(Collectors.toList());
}
}
Expand Up @@ -249,7 +249,7 @@ private long resolveValue(String s) {
}
}

private String resolveVariable(String s) {
protected String resolveVariable(String s) {
if (s == null) {
return null;
}
Expand Down
Expand Up @@ -16,22 +16,14 @@

package org.jbpm.workflow.instance.node;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;

import org.drools.core.common.InternalKnowledgeRuntime;
import org.drools.core.util.MVELSafeHelper;
import org.jbpm.process.core.context.variable.VariableScope;
import org.jbpm.process.core.timer.BusinessCalendar;
import org.jbpm.process.core.timer.Timer;
import org.jbpm.process.instance.InternalProcessRuntime;
import org.jbpm.process.instance.ProcessInstance;
import org.jbpm.process.instance.context.variable.VariableScopeInstance;
import org.jbpm.process.instance.timer.TimerInstance;
import org.jbpm.workflow.core.node.TimerNode;
import org.jbpm.workflow.instance.WorkflowProcessInstance;
import org.jbpm.workflow.instance.impl.NodeInstanceResolverFactory;
import org.kie.api.runtime.process.EventListener;
import org.kie.api.runtime.process.NodeInstance;
import org.slf4j.Logger;
Expand Down Expand Up @@ -95,37 +87,7 @@ protected TimerInstance createTimerInstance(InternalKnowledgeRuntime kruntime) {
timerInstance.setTimerId(timer.getId());
return timerInstance;
}

private String resolveVariable(String s) {
Map<String, String> replacements = new HashMap<String, String>();
Matcher matcher = PARAMETER_MATCHER.matcher(s);
while (matcher.find()) {
String paramName = matcher.group(1);
if (replacements.get(paramName) == null) {
VariableScopeInstance variableScopeInstance = (VariableScopeInstance)
resolveContextInstance(VariableScope.VARIABLE_SCOPE, paramName);
if (variableScopeInstance != null) {
Object variableValue = variableScopeInstance.getVariable(paramName);
String variableValueString = variableValue == null ? "" : variableValue.toString();
replacements.put(paramName, variableValueString);
} else {
try {
Object variableValue = MVELSafeHelper.getEvaluator().eval(paramName, new NodeInstanceResolverFactory(this));
String variableValueString = variableValue == null ? "" : variableValue.toString();
replacements.put(paramName, variableValueString);
} catch (Throwable t) {
logger.error("Could not find variable scope for variable {}", paramName);
logger.error("when trying to replace variable in processId for sub process {}", getNodeName());
logger.error("Continuing without setting process id.");
}
}
}
}
for (Map.Entry<String, String> replacement: replacements.entrySet()) {
s = s.replace("#{" + replacement.getKey() + "}", replacement.getValue());
}
return s;
}

public void signalEvent(String type, Object event) {
if ("timerTriggered".equals(type)) {
TimerInstance timer = (TimerInstance) event;
Expand Down

0 comments on commit a2893d3

Please sign in to comment.