From e9b3f4af7ceffc70eb1b0a86659fd22a5a561c37 Mon Sep 17 00:00:00 2001 From: Enrique Gonzalez Martinez Date: Tue, 22 Oct 2024 11:38:27 +0200 Subject: [PATCH 1/3] [incubator-kie-issues-1555] Multiple sub process instances cancelled by the same timer. --- .../DefaultCountDownProcessEventListener.java | 11 + .../process/core/event/EventTypeFilter.java | 112 ++++++-- .../actions/AbstractNodeInstanceAction.java | 4 +- .../ruleflow/core/RuleFlowProcessFactory.java | 7 +- .../impl/WorkflowProcessInstanceImpl.java | 197 +++++++++++--- .../node/BoundaryEventNodeInstance.java | 9 +- .../instance/node/CompositeNodeInstance.java | 15 +- .../instance/node/StateBasedNodeInstance.java | 7 +- .../instance/node/TimerNodeInstance.java | 3 +- ...iInstanceLoopSubprocessBoundaryTimer.bpmn2 | 245 ++++++++++++++++++ .../org/jbpm/bpmn2/IntermediateEventTest.java | 34 ++- .../impl/DefaultUserTaskInstance.java | 4 +- 12 files changed, 568 insertions(+), 80 deletions(-) create mode 100644 jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/loop/BPMN2-MultiInstanceLoopSubprocessBoundaryTimer.bpmn2 diff --git a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/test/util/DefaultCountDownProcessEventListener.java b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/test/util/DefaultCountDownProcessEventListener.java index b00a151e446..7f9e62e87f7 100644 --- a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/test/util/DefaultCountDownProcessEventListener.java +++ b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/test/util/DefaultCountDownProcessEventListener.java @@ -43,6 +43,17 @@ public boolean waitTillCompleted() { return waitTillCompleted(10000); } + public void await() { + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.error("Interrputed thread while waiting for all triggers", e); + } catch (Exception e) { + logger.error("Error during waiting state", e); + } + } + public boolean waitTillCompleted(long timeOut) { try { return latch.await(timeOut, TimeUnit.MILLISECONDS); diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/event/EventTypeFilter.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/event/EventTypeFilter.java index 01ee3388e2a..6868325465f 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/event/EventTypeFilter.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/event/EventTypeFilter.java @@ -19,10 +19,19 @@ package org.jbpm.process.core.event; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Function; +import java.util.regex.Matcher; import org.jbpm.process.core.correlation.CorrelationInstance; import org.jbpm.process.core.correlation.CorrelationManager; +import org.jbpm.util.PatternConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,37 +64,106 @@ public void setType(String type) { this.type = type; } + public void setMessageRef(String messageRef) { + this.messageRef = messageRef; + } + public String toString() { return "Event filter: [" + this.type + "]"; } @Override public boolean acceptsEvent(String type, Object event, Function resolver) { - logger.debug("This event is subscribed to a message type {} with payload {}", type, event); + if (this.type == null) { + return false; + } + if (resolver == null) { - return this.type != null && this.type.equals(type); + return this.type.equals(type); } - if (this.type != null && this.type.equals(type)) { - if (correlationManager != null && correlationManager.isSubscribe(messageRef)) { - if (event == null) { - logger.debug("This event is subscribed to a message ref {}", type); - return false; - } - CorrelationInstance messageCorrelation = correlationManager.computeCorrelationInstance(messageRef, event); - CorrelationInstance processCorrelation = correlationManager.computeSubscription(messageRef, resolver); - logger.debug("The event type {} is correlated, computing correlations. Message correlation is {}; process correlation is: {} ", type, messageCorrelation, processCorrelation); - return messageCorrelation.equals(processCorrelation); + if (this.type.equals(type) && correlationManager != null && correlationManager.isSubscribe(messageRef)) { + logger.debug("This event is subscribed to a message type {} with payload {}", type, event); + if (event == null) { + logger.debug("Cannot compute subscription for messageref {} and type {}", messageRef, type); + return false; } - return true; + CorrelationInstance messageCorrelation = correlationManager.computeCorrelationInstance(messageRef, event); + CorrelationInstance processCorrelation = correlationManager.computeSubscription(messageRef, resolver); + logger.debug("The event type {} is correlated, computing correlations. Message correlation is {}; process correlation is: {} ", type, messageCorrelation, processCorrelation); + return messageCorrelation.equals(processCorrelation); } - String resolvedType = (String) resolver.apply(this.type); - return resolvedType != null && resolvedType.equals(type); + return isAccepted(type, resolver); } - public void setMessageRef(String messageRef) { - this.messageRef = messageRef; + public boolean isAccepted(String type, Function resolver) { + return resolveVariable(this.type, resolver).contains(type); } + + private List resolveVariable(String varExpression, Function resolver) { + if (varExpression == null) { + return Collections.emptyList(); + } + Map replacements = new HashMap<>(); + Matcher matcher = PatternConstants.PARAMETER_MATCHER.matcher(varExpression); + while (matcher.find()) { + String paramName = matcher.group(1); + Object value = resolver.apply(paramName); + if (value == null) { + logger.warn("expression {} in dynamic signal {} not resolved", paramName, varExpression); + continue; + } else if (value instanceof Object[]) { + replacements.put(paramName, (Object[]) value); + } else { + replacements.put(paramName, new Object[] { value }); + } + } + List acceptedTypes = new ArrayList<>(); + List> data = generateCombinations(replacements.keySet(), replacements); + for (Map combination : data) { + String tmp = varExpression; + for (Map.Entry replacement : combination.entrySet()) { + tmp = tmp.replace("#{" + replacement.getKey() + "}", replacement.getValue()); + } + acceptedTypes.add(tmp); + } + if (acceptedTypes.isEmpty()) { + acceptedTypes.add(varExpression); + } + return acceptedTypes; + } + + private List> generateCombinations(Set keys, Map data) { + List> combinations = new ArrayList<>(); + for (String key : keys) { + Set remaining = new HashSet<>(keys); + remaining.remove(key); + List> subCombinations = generateCombinations(remaining, data); + if (subCombinations.isEmpty()) { + for (Object value : data.get(key)) { + Map combination = new HashMap<>(); + combination.put(key, value.toString()); + if (!combinations.contains(combination)) { + combinations.add(combination); + } + } + } else { + for (Map subCombination : subCombinations) { + for (Object value : data.get(key)) { + Map combination = new HashMap<>(); + combination.putAll(subCombination); + combination.put(key, value.toString()); + if (!combinations.contains(combination)) { + combinations.add(combination); + } + } + } + } + } + + return combinations; + } + } diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/impl/actions/AbstractNodeInstanceAction.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/impl/actions/AbstractNodeInstanceAction.java index 569a3faceb3..aa1d5756e7d 100644 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/impl/actions/AbstractNodeInstanceAction.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/impl/actions/AbstractNodeInstanceAction.java @@ -24,7 +24,7 @@ import org.jbpm.process.instance.impl.Action; import org.jbpm.workflow.instance.node.CompositeNodeInstance; import org.kie.api.runtime.process.NodeInstance; -import org.kie.api.runtime.process.WorkflowProcessInstance; +import org.kie.api.runtime.process.NodeInstanceContainer; import org.kie.kogito.internal.process.runtime.KogitoProcessContext; public abstract class AbstractNodeInstanceAction implements Action, Serializable { @@ -39,7 +39,7 @@ protected AbstractNodeInstanceAction(String attachedToNodeId) { @Override public void execute(KogitoProcessContext context) throws Exception { - WorkflowProcessInstance pi = context.getNodeInstance().getProcessInstance(); + NodeInstanceContainer pi = context.getNodeInstance().getNodeInstanceContainer(); NodeInstance nodeInstance = findNodeByUniqueId(pi.getNodeInstances(), attachedToNodeId); if (nodeInstance != null) { execute(nodeInstance); diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/ruleflow/core/RuleFlowProcessFactory.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/ruleflow/core/RuleFlowProcessFactory.java index ea9a67f0a9f..2234646210d 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/ruleflow/core/RuleFlowProcessFactory.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/ruleflow/core/RuleFlowProcessFactory.java @@ -42,6 +42,7 @@ import org.jbpm.process.instance.impl.ReturnValueEvaluator; import org.jbpm.process.instance.impl.actions.CancelNodeInstanceAction; import org.jbpm.process.instance.impl.actions.SignalProcessInstanceAction; +import org.jbpm.process.instance.impl.util.VariableUtil; import org.jbpm.ruleflow.core.validation.RuleFlowProcessValidator; import org.jbpm.workflow.core.DroolsAction; import org.jbpm.workflow.core.WorkflowModelValidator; @@ -412,8 +413,10 @@ protected void linkBoundaryErrorEvent(Node node, String attachedTo, Node attache protected DroolsAction timerAction(String type) { DroolsAction signal = new DroolsAction(); - - Action action = kcontext -> kcontext.getProcessInstance().signalEvent(type, kcontext.getNodeInstance().getStringId()); + Action action = kcontext -> { + String eventType = VariableUtil.resolveVariable(type, kcontext.getNodeInstance()); + kcontext.getProcessInstance().signalEvent(eventType, kcontext.getNodeInstance().getStringId()); + }; signal.wire(action); return signal; diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java index 25a0cd0467f..48a7276e269 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java @@ -34,11 +34,13 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.stream.Collectors; import java.util.stream.Stream; import org.drools.core.common.InternalKnowledgeRuntime; +import org.drools.mvel.MVELSafeHelper; import org.drools.mvel.util.MVELEvaluator; import org.jbpm.process.core.ContextContainer; import org.jbpm.process.core.ContextResolver; @@ -57,16 +59,19 @@ import org.jbpm.workflow.core.Node; import org.jbpm.workflow.core.impl.NodeImpl; import org.jbpm.workflow.core.node.BoundaryEventNode; +import org.jbpm.workflow.core.node.CompositeContextNode; import org.jbpm.workflow.core.node.CompositeNode; import org.jbpm.workflow.core.node.DynamicNode; import org.jbpm.workflow.core.node.EventNode; import org.jbpm.workflow.core.node.EventNodeInterface; import org.jbpm.workflow.core.node.EventSubProcessNode; +import org.jbpm.workflow.core.node.ForEachNode; import org.jbpm.workflow.core.node.MilestoneNode; import org.jbpm.workflow.core.node.StartNode; import org.jbpm.workflow.core.node.StateNode; import org.jbpm.workflow.instance.NodeInstance; import org.jbpm.workflow.instance.WorkflowProcessInstance; +import org.jbpm.workflow.instance.node.CompositeContextNodeInstance; import org.jbpm.workflow.instance.node.CompositeNodeInstance; import org.jbpm.workflow.instance.node.EndNodeInstance; import org.jbpm.workflow.instance.node.EventBasedNodeInstanceInterface; @@ -74,6 +79,7 @@ import org.jbpm.workflow.instance.node.EventNodeInstanceInterface; import org.jbpm.workflow.instance.node.EventSubProcessNodeInstance; import org.jbpm.workflow.instance.node.FaultNodeInstance; +import org.jbpm.workflow.instance.node.ForEachNodeInstance; import org.jbpm.workflow.instance.node.StateBasedNodeInstance; import org.jbpm.workflow.instance.node.WorkItemNodeInstance; import org.kie.api.definition.process.NodeContainer; @@ -99,6 +105,7 @@ import org.kie.kogito.process.flexible.Milestone; import org.kie.kogito.timer.TimerInstance; import org.mvel2.integration.VariableResolverFactory; +import org.mvel2.integration.impl.ImmutableDefaultFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -291,12 +298,12 @@ public List getNodeInstances(WorkflowElementIdentifier nodeId) { return result; } - public List getNodeInstances(WorkflowElementIdentifier nodeId, final List currentView) { + public List getNodeInstances(WorkflowElementIdentifier nodeId, final List currentView) { if (nodeId == null) { return Collections.emptyList(); } - List result = new ArrayList<>(); - for (final NodeInstance nodeInstance : currentView) { + List result = new ArrayList<>(); + for (org.kie.api.runtime.process.NodeInstance nodeInstance : currentView) { if (nodeId.equals(nodeInstance.getNodeId())) { result.add(nodeInstance); } @@ -677,8 +684,6 @@ public void signalEvent(String type, Object event) { return; } - List currentView = new ArrayList<>(this.nodeInstances); - try { this.activatingNodeIds = new ArrayList<>(); List listeners = eventListeners.get(type); @@ -693,32 +698,8 @@ public void signalEvent(String type, Object event) { listener.signalEvent(type, event); } } - for (org.kie.api.definition.process.Node node : getWorkflowProcess().getNodes()) { - if (node instanceof EventNodeInterface && ((EventNodeInterface) node).acceptsEvent(type, event, getResolver(node, currentView))) { - if (node instanceof BoundaryEventNode boundaryEventNode) { - WorkflowElementIdentifier id = WorkflowElementIdentifierFactory.fromExternalFormat(boundaryEventNode.getAttachedToNodeId()); - if (!getNodeInstances(id, currentView).isEmpty()) { - EventNodeInstance eventNodeInstance = (EventNodeInstance) getNodeInstance(node); - eventNodeInstance.signalEvent(type, event, getResolver(node, currentView)); - } else if (type.startsWith("Error-") || type.startsWith("Compensation-") || type.startsWith("implicit:compensation")) { - EventNodeInstance eventNodeInstance = (EventNodeInstance) getNodeInstance(node); - eventNodeInstance.signalEvent(type, event, getResolver(node, currentView)); - } - } else { - if (node instanceof EventSubProcessNode && (resolveVariables(((EventSubProcessNode) node).getEvents()).contains(type))) { - EventSubProcessNodeInstance eventNodeInstance = (EventSubProcessNodeInstance) getNodeInstance(node); - eventNodeInstance.signalEvent(type, event); - } else { - List nodeInstances = getNodeInstances(node.getId(), currentView); - if (nodeInstances != null && !nodeInstances.isEmpty()) { - for (NodeInstance nodeInstance : nodeInstances) { - ((EventNodeInstanceInterface) nodeInstance).signalEvent(type, event, getResolver(node, currentView)); - } - } - } - } - } - } + + signal(this, (node) -> this.getNodeInstance(node), () -> this.getWorkflowProcess().getNodes(), type, event); if (((org.jbpm.workflow.core.WorkflowProcess) getWorkflowProcess()).isDynamic()) { for (org.kie.api.definition.process.Node node : getWorkflowProcess().getNodes()) { @@ -749,28 +730,168 @@ public void signalEvent(String type, Object event) { } } - private Function getResolver(org.kie.api.definition.process.Node node, List currentView) { + private void signal(org.kie.api.runtime.process.NodeInstanceContainer container, Function nodeInstanceSupplier, + Supplier resolveNodes, String type, Object event) { + + List currentView = container.getNodeInstances().stream().map(NodeInstance.class::cast).collect(Collectors.toList()); + for (org.kie.api.definition.process.Node node : resolveNodes.get()) { + if (node instanceof EventNodeInterface && ((EventNodeInterface) node).acceptsEvent(type, event, getEventFilterResolver(container, node, currentView))) { + if (node instanceof BoundaryEventNode boundaryEventNode) { + WorkflowElementIdentifier id = WorkflowElementIdentifierFactory.fromExternalFormat(boundaryEventNode.getAttachedToNodeId()); + if (!getNodeInstances(id, currentView).isEmpty()) { + EventNodeInstance eventNodeInstance = (EventNodeInstance) nodeInstanceSupplier.apply(node); + eventNodeInstance.signalEvent(type, event, getEventFilterResolver(container, node, currentView)); + } else if (type.startsWith("Error-") || type.startsWith("Compensation-") || type.startsWith("implicit:compensation")) { + EventNodeInstance eventNodeInstance = (EventNodeInstance) nodeInstanceSupplier.apply(node); + eventNodeInstance.signalEvent(type, event, getEventFilterResolver(container, node, currentView)); + } + } else { + if (node instanceof EventSubProcessNode && (resolveVariables(((EventSubProcessNode) node).getEvents()).contains(type))) { + EventSubProcessNodeInstance eventNodeInstance = (EventSubProcessNodeInstance) getNodeInstance(node); + eventNodeInstance.signalEvent(type, event); + } else { + List nodeInstances = getNodeInstances(node.getId(), currentView); + if (nodeInstances != null && !nodeInstances.isEmpty()) { + for (org.kie.api.runtime.process.NodeInstance nodeInstance : nodeInstances) { + ((EventNodeInstanceInterface) nodeInstance).signalEvent(type, event, getEventFilterResolver(container, node, currentView)); + } + } + } + } + } + } + } + + public Function getEventFilterResolver(org.kie.api.runtime.process.NodeInstanceContainer container, org.kie.api.definition.process.Node node, + List currentView) { if (node instanceof DynamicNode) { // special handling for dynamic node to allow to resolve variables from individual node instances of the dynamic node // instead of just relying on process instance's variables - return e -> { - List nodeInstances = getNodeInstances(node.getId(), currentView); + return (varExpresion) -> { + List nodeInstances = getNodeInstances(node.getId(), currentView); if (nodeInstances != null && !nodeInstances.isEmpty()) { StringBuilder st = new StringBuilder(); - for (NodeInstance ni : nodeInstances) { - Object result = resolveVariable(e, new NodeInstanceResolverFactory(ni)); + for (org.kie.api.runtime.process.NodeInstance ni : nodeInstances) { + Object result = resolveExpressionVariable(varExpresion, new NodeInstanceResolverFactory((NodeInstance) ni)); st.append(result).append("###"); } return st.toString(); } else { - return resolveVariable(e); + NodeInstanceImpl instance = (NodeInstanceImpl) getNodeInstance(node.getId().toExternalFormat(), true); + if (instance != null) { + return instance.getVariable(varExpresion); + } + return null; + } + }; + } else if (node instanceof BoundaryEventNode) { + return (varExpresion) -> { + Function getScopedVariable; + if (container instanceof CompositeContextNodeInstance) { + getScopedVariable = (name) -> getVariable(name, ((CompositeContextNodeInstance) container).getContextInstances(VariableScope.VARIABLE_SCOPE)); + } else if (container instanceof WorkflowProcessInstanceImpl) { + getScopedVariable = (name) -> ((WorkflowProcessInstanceImpl) container).getVariable(name); + } else { + getScopedVariable = null; + } + Object value = getScopedVariable.apply(varExpresion); + if (value != null) { + return value; } + VariableResolverFactory resolverFactory = new ImmutableDefaultFactory() { + @Override + public boolean isResolveable(String varName) { + return getScopedVariable.apply(varName) != null; + } + + @Override + public org.mvel2.integration.VariableResolver getVariableResolver(String varName) { + return new org.mvel2.integration.impl.SimpleValueResolver(getScopedVariable.apply(varName)); + } + }; + return resolveExpressionVariable(varExpresion, resolverFactory).orElse(null); + }; + } else if (node instanceof ForEachNode) { + return (varExpression) -> { + try { + // for each can have multiple outcomes 1 per item of the list so it should be computed like that + ForEachNodeInstance forEachNodeInstance = (ForEachNodeInstance) getNodeInstanceByNodeId(node.getId(), true); + if (forEachNodeInstance == null) { + return new Object[0]; + } + List data = forEachNodeInstance.getNodeInstances().stream().filter(e -> e instanceof CompositeContextNodeInstance) + .map(e -> (CompositeContextNodeInstance) e).collect(Collectors.toList()); + List outcome = new ArrayList<>(); + for (CompositeContextNodeInstance nodeInstance : data) { + Object resolvedValue = resolveExpressionVariable(varExpression, new NodeInstanceResolverFactory(nodeInstance)).orElse(null); + if (resolvedValue != null) { + outcome.add(resolvedValue); + } + } + return outcome.toArray(); + } catch (Throwable t) { + return new Object[0]; + } + }; + } else if (node instanceof EventSubProcessNode || node instanceof StateNode) { + return (varName) -> { + return resolveExpressionVariable(varName, new ProcessInstanceResolverFactory(this)).orElse(null); + }; + } else if (node instanceof CompositeContextNode) { + return (varExpression) -> { + List nodeInstances = getNodeInstances(node.getId(), currentView); + List outcome = new ArrayList<>(); + if (nodeInstances != null && !nodeInstances.isEmpty()) { + for (org.kie.api.runtime.process.NodeInstance nodeInstance : nodeInstances) { + Object resolvedValue = resolveExpressionVariable(varExpression, new NodeInstanceResolverFactory((NodeInstance) nodeInstance)).orElse(null); + if (resolvedValue != null) { + outcome.add(resolvedValue); + } + } + } + return outcome.toArray(); }; } else { - return this::resolveVariable; + return (varName) -> { + return resolveExpressionVariable(varName, new ProcessInstanceResolverFactory(this)).orElse(null); + }; + } + } + + public Object getVariable(String name, List variableScopeInstances) { + if (variableScopeInstances != null) { + for (ContextInstance contextInstance : variableScopeInstances) { + Object value = ((VariableScopeInstance) contextInstance).getVariable(name); + if (value != null) { + return value; + } + } + } + return null; + } + + private Optional resolveExpressionVariable(String paramName, VariableResolverFactory factory) { + try { + // just in case is not an expression + if (factory.isResolveable(paramName)) { + return Optional.of(factory.getVariableResolver(paramName).getValue()); + } + return Optional.ofNullable(MVELSafeHelper.getEvaluator().eval(paramName, factory)); + } catch (Throwable t) { + logger.error("Could not find variable scope for variable {}", paramName); + return Optional.empty(); } } + public NodeInstance getNodeInstanceByNodeId(WorkflowElementIdentifier nodeId, boolean recursive) { + for (NodeInstance nodeInstance : getNodeInstances(recursive)) { + if (nodeInstance.getNodeId().equals(nodeId)) { + return nodeInstance; + } + } + return null; + } + protected List resolveVariables(List events) { return events.stream().map(this::resolveVariable).map(Object::toString).collect(Collectors.toList()); } diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/BoundaryEventNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/BoundaryEventNodeInstance.java index e4fbc359594..d8912fe7ea4 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/BoundaryEventNodeInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/BoundaryEventNodeInstance.java @@ -24,7 +24,6 @@ import org.jbpm.ruleflow.core.Metadata; import org.jbpm.workflow.core.node.BoundaryEventNode; -import org.jbpm.workflow.instance.NodeInstance; import org.jbpm.workflow.instance.NodeInstanceContainer; import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl; import org.slf4j.Logger; @@ -44,7 +43,7 @@ public void signalEvent(String type, Object event, Function varR BoundaryEventNode boundaryNode = (BoundaryEventNode) getEventNode(); String attachedTo = boundaryNode.getAttachedToNodeId(); - Collection nodeInstances = getProcessInstance().getNodeInstances(true); + Collection nodeInstances = getNodeInstanceContainer().getNodeInstances(); if (type != null && type.startsWith(Metadata.EVENT_TYPE_COMPENSATION)) { // if not active && completed, signal if (!isAttachedToNodeActive(nodeInstances, attachedTo, type, event) && isAttachedToNodeCompleted(attachedTo)) { @@ -66,15 +65,15 @@ public void signalEvent(String type, Object event) { this.signalEvent(type, event, varName -> this.getVariable(varName)); } - private boolean isAttachedToNodeActive(Collection nodeInstances, String attachedTo, String type, Object event) { + private boolean isAttachedToNodeActive(Collection nodeInstances, String attachedTo, String type, Object event) { if (nodeInstances != null && !nodeInstances.isEmpty()) { - for (NodeInstance nInstance : nodeInstances) { + for (org.kie.api.runtime.process.NodeInstance nInstance : nodeInstances) { String nodeUniqueId = (String) nInstance.getNode().getUniqueId(); boolean isActivating = ((WorkflowProcessInstanceImpl) nInstance.getProcessInstance()).getActivatingNodeIds().contains(nodeUniqueId); if (attachedTo.equals(nodeUniqueId) && !isActivating) { // in case this is timer event make sure it corresponds to the proper node instance if (type.startsWith("Timer-")) { - if (nInstance.getStringId().equals(event)) { + if (nInstance.getId().equals(event)) { return true; } } else { diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/CompositeNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/CompositeNodeInstance.java index ea9a6774629..c926ba21a32 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/CompositeNodeInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/CompositeNodeInstance.java @@ -41,6 +41,7 @@ import org.jbpm.workflow.instance.impl.NodeInstanceFactory; import org.jbpm.workflow.instance.impl.NodeInstanceFactoryRegistry; import org.jbpm.workflow.instance.impl.NodeInstanceImpl; +import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl; import org.kie.api.definition.process.Connection; import org.kie.api.definition.process.NodeContainer; import org.kie.api.definition.process.WorkflowElementIdentifier; @@ -305,19 +306,19 @@ private NodeInstance buildCompositeNodeInstance(NodeInstanceImpl nodeInstance, o @Override public void signalEvent(String type, Object event, Function varResolver) { - List currentView = new ArrayList<>(this.nodeInstances); + List currentView = new ArrayList<>(this.nodeInstances); super.signalEvent(type, event); for (org.kie.api.definition.process.Node node : getCompositeNode().internalGetNodes()) { if (node instanceof EventNodeInterface - && ((EventNodeInterface) node).acceptsEvent(type, event, varName -> this.getVariable(varName))) { + && ((EventNodeInterface) node).acceptsEvent(type, event, ((WorkflowProcessInstanceImpl) this.getProcessInstance()).getEventFilterResolver(this, node, currentView))) { if (node instanceof EventNode && ((EventNode) node).getFrom() == null || node instanceof EventSubProcessNode) { EventNodeInstanceInterface eventNodeInstance = (EventNodeInstanceInterface) getNodeInstance(node); eventNodeInstance.signalEvent(type, event, varResolver); } else { - List nodeInstances = getNodeInstances(node.getId(), currentView); + List nodeInstances = getNodeInstances(node.getId(), currentView); if (nodeInstances != null && !nodeInstances.isEmpty()) { - for (NodeInstance nodeInstance : nodeInstances) { + for (org.kie.api.runtime.process.NodeInstance nodeInstance : nodeInstances) { ((EventNodeInstanceInterface) nodeInstance).signalEvent(type, event, varResolver); } } @@ -354,9 +355,9 @@ public List getNodeInstances(WorkflowElementIdentifier nodeId) { return result; } - public List getNodeInstances(WorkflowElementIdentifier nodeId, List currentView) { - List result = new ArrayList<>(); - for (final NodeInstance nodeInstance : currentView) { + public List getNodeInstances(WorkflowElementIdentifier nodeId, List currentView) { + List result = new ArrayList<>(); + for (org.kie.api.runtime.process.NodeInstance nodeInstance : currentView) { if (nodeInstance.getNodeId().equals(nodeId)) { result.add(nodeInstance); } diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/StateBasedNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/StateBasedNodeInstance.java index 6b69126d81c..2b0517718c6 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/StateBasedNodeInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/StateBasedNodeInstance.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import org.drools.core.common.InternalAgenda; import org.drools.core.common.ReteEvaluator; @@ -100,11 +99,11 @@ public void internalTrigger(KogitoNodeInstance from, String type) { .generateId() .timerId(Long.toString(timer.getId())) .expirationTime(createTimerInstance(timer)) - .processInstanceId(getProcessInstance().getStringId()) + .rootProcessId(getProcessInstance().getRootProcessId()) .rootProcessInstanceId(getProcessInstance().getRootProcessInstanceId()) .processId(getProcessInstance().getProcessId()) - .rootProcessId(getProcessInstance().getRootProcessId()) - .nodeInstanceId(Optional.ofNullable(from).map(KogitoNodeInstance::getStringId).orElse(null)) + .processInstanceId(getProcessInstance().getStringId()) + .nodeInstanceId(this.getId()) .build(); String jobId = jobService.scheduleProcessInstanceJob(jobDescription); timerInstances.add(jobId); diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/TimerNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/TimerNodeInstance.java index 054ef0ef8de..452e9785287 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/TimerNodeInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/TimerNodeInstance.java @@ -23,7 +23,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -91,7 +90,7 @@ public void internalTrigger(KogitoNodeInstance from, String type) { .rootProcessInstanceId(getProcessInstance().getRootProcessInstanceId()) .processId(getProcessInstance().getProcessId()) .rootProcessId(getProcessInstance().getRootProcessId()) - .nodeInstanceId(Optional.ofNullable(from).map(KogitoNodeInstance::getStringId).orElse(null)) + .nodeInstanceId(this.getId()) .build(); JobsService jobService = processRuntime.getJobsService(); String jobId = jobService.scheduleProcessInstanceJob(jobDescription); diff --git a/jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/loop/BPMN2-MultiInstanceLoopSubprocessBoundaryTimer.bpmn2 b/jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/loop/BPMN2-MultiInstanceLoopSubprocessBoundaryTimer.bpmn2 new file mode 100644 index 00000000000..5afcff04cbd --- /dev/null +++ b/jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/loop/BPMN2-MultiInstanceLoopSubprocessBoundaryTimer.bpmn2 @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _E51B394B-06A3-4AF6-BC86-157D9039A0FB + + + _D296A998-04B5-4CB2-9CFE-1D19C729D5B8 + + + + + + + + _D296A998-04B5-4CB2-9CFE-1D19C729D5B8 + _E51B394B-06A3-4AF6-BC86-157D9039A0FB + + + + + _EEFE61C3-C4A3-48D3-9971-D499F41E88CC_IN_COLLECTIONInputX + _EEFE61C3-C4A3-48D3-9971-D499F41E88CC_itemInputX + + + + + mi_input + _EEFE61C3-C4A3-48D3-9971-D499F41E88CC_IN_COLLECTIONInputX + + + _EEFE61C3-C4A3-48D3-9971-D499F41E88CC_IN_COLLECTIONInputX + + + + + + + + + + + + + + + + + + + + + + + + + + + + _A75E00C6-6FBD-48DE-9750-462DC521483A + _DB8BA8C1-DE12-479A-AF7D-7A562718926C + _CA5FFA7C-D186-4CCB-BC1C-134145DD3C0A + + + + + + + + _DFAE59C0-EE21-4991-AF31-BEBFD37BEB98 + _DB8BA8C1-DE12-479A-AF7D-7A562718926C + System.out.println("Script Timer Task!"); + + + + + + + + _72B473CD-9F5C-461D-AC1B-DCEE85A38365 + _A75E00C6-6FBD-48DE-9750-462DC521483A + + + + + _DEF3687C-875E-4F22-B764-AB6C5626902D_TaskNameInputX + _DEF3687C-875E-4F22-B764-AB6C5626902D_SkippableInputX + + + + _DEF3687C-875E-4F22-B764-AB6C5626902D_TaskNameInputX + + + + + + + _DEF3687C-875E-4F22-B764-AB6C5626902D_SkippableInputX + + + + + + + + admin + + + + + + + + + + _2FC3736F-E7E6-446C-87B5-5B018FF20372 + _72B473CD-9F5C-461D-AC1B-DCEE85A38365 + System.out.println("Script Task: " + item); + + + _CA5FFA7C-D186-4CCB-BC1C-134145DD3C0A + + + _2FC3736F-E7E6-446C-87B5-5B018FF20372 + + + _DFAE59C0-EE21-4991-AF31-BEBFD37BEB98 + + #{item} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _Nw1jAGpCED2xyqiTIWNwhw + _Nw1jAGpCED2xyqiTIWNwhw + + diff --git a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java index 9d2ec7cc3b5..c9e1f0a7ed9 100755 --- a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java +++ b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java @@ -111,6 +111,8 @@ import org.jbpm.bpmn2.loop.MultiInstanceLoopCharacteristicsTaskSequentialProcess; import org.jbpm.bpmn2.loop.MultiInstanceLoopCharacteristicsTaskWithOutputCmpCondSequentialModel; import org.jbpm.bpmn2.loop.MultiInstanceLoopCharacteristicsTaskWithOutputCmpCondSequentialProcess; +import org.jbpm.bpmn2.loop.MultiInstanceLoopSubprocessBoundaryTimerModel; +import org.jbpm.bpmn2.loop.MultiInstanceLoopSubprocessBoundaryTimerProcess; import org.jbpm.bpmn2.objects.Person; import org.jbpm.bpmn2.objects.TestUserTaskWorkItemHandler; import org.jbpm.bpmn2.objects.TestWorkItemHandler; @@ -144,6 +146,7 @@ import org.jbpm.test.utils.ProcessTestHelper.CompletionKogitoEventListener; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import org.kie.api.command.ExecutableCommand; import org.kie.api.event.process.ProcessCompletedEvent; import org.kie.api.event.process.ProcessNodeLeftEvent; @@ -481,6 +484,7 @@ public void testEventBasedSplitAfter() { } @Test + @Timeout(10000L) public void testEventBasedSplit2() { ProcessCompletedCountDownProcessEventListener countDownListener = new ProcessCompletedCountDownProcessEventListener(1); Application app = ProcessTestHelper.newApplication(); @@ -511,7 +515,7 @@ public void testEventBasedSplit2() { instance = processDefinition.createInstance(model); instance.start(); assertThat(instance.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_ACTIVE); - countDownListener.waitTillCompleted(); + countDownListener.await(); assertThat(instance.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_COMPLETED); } @@ -2037,6 +2041,34 @@ public void testMultiInstanceLoopBoundaryTimer() throws Exception { assertThat(instance.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_COMPLETED); } + @Test + @Timeout(10000L) + public void testMultiInstanceLoopSubprocessBoundaryTimer() throws Exception { + Application app = ProcessTestHelper.newApplication(); + NodeLeftCountDownProcessEventListener countDownListener = new NodeLeftCountDownProcessEventListener("Script2", 1); + ProcessTestHelper.registerProcessEventListener(app, countDownListener); + TestUserTaskWorkItemHandler handler = new TestUserTaskWorkItemHandler(); + ProcessTestHelper.registerHandler(app, "Human Task", handler); + + org.kie.kogito.process.Process definition = MultiInstanceLoopSubprocessBoundaryTimerProcess.newProcess(app); + MultiInstanceLoopSubprocessBoundaryTimerModel model = definition.createModel(); + model.setMi_input(List.of("PT1S", "PT2S", "PT3S")); + org.kie.kogito.process.ProcessInstance instance = definition.createInstance(model); + instance.start(); + + countDownListener.reset(1); + countDownListener.await(); + assertThat(instance.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_ACTIVE); + + countDownListener.reset(1); + countDownListener.await(); + assertThat(instance.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_ACTIVE); + + countDownListener.reset(1); + countDownListener.await(); + assertThat(instance.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_COMPLETED); + } + @Test public void testMultiInstanceLoopCharacteristicsProcessSequential() throws Exception { Application app = ProcessTestHelper.newApplication(); diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java index 266e674887e..757bf30edec 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java @@ -210,7 +210,7 @@ public void setOutputs(Map outputs) { public void setInput(String key, Object newValue) { Object oldValue = this.inputs.put(key, newValue); if (this.userTaskEventSupport != null) { - this.userTaskEventSupport.fireOnUserTaskInputVariableChange(this, key, oldValue, newValue); + this.userTaskEventSupport.fireOnUserTaskInputVariableChange(this, key, newValue, oldValue); } updatePersistence(); } @@ -219,7 +219,7 @@ public void setInput(String key, Object newValue) { public void setOutput(String key, Object newValue) { Object oldValue = this.outputs.put(key, newValue); if (this.userTaskEventSupport != null) { - this.userTaskEventSupport.fireOnUserTaskOutputVariableChange(this, key, oldValue, newValue); + this.userTaskEventSupport.fireOnUserTaskOutputVariableChange(this, key, newValue, oldValue); } updatePersistence(); } From 9910c702501c4ded539061dccf4e015b46017d30 Mon Sep 17 00:00:00 2001 From: Enrique Gonzalez Martinez Date: Thu, 31 Oct 2024 12:48:48 +0100 Subject: [PATCH 2/3] fix tests --- .../test/util/DefaultCountDownProcessEventListener.java | 5 ++++- .../src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/test/util/DefaultCountDownProcessEventListener.java b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/test/util/DefaultCountDownProcessEventListener.java index 7f9e62e87f7..777a972eac0 100644 --- a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/test/util/DefaultCountDownProcessEventListener.java +++ b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/test/util/DefaultCountDownProcessEventListener.java @@ -43,14 +43,17 @@ public boolean waitTillCompleted() { return waitTillCompleted(10000); } - public void await() { + public boolean await() { try { latch.await(); + return true; } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.error("Interrputed thread while waiting for all triggers", e); + return false; } catch (Exception e) { logger.error("Error during waiting state", e); + return false; } } diff --git a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java index c9e1f0a7ed9..bace4169053 100755 --- a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java +++ b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java @@ -2057,15 +2057,15 @@ public void testMultiInstanceLoopSubprocessBoundaryTimer() throws Exception { instance.start(); countDownListener.reset(1); - countDownListener.await(); + assertThat(countDownListener.await()).isTrue(); assertThat(instance.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_ACTIVE); countDownListener.reset(1); - countDownListener.await(); + assertThat(countDownListener.await()).isTrue(); assertThat(instance.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_ACTIVE); countDownListener.reset(1); - countDownListener.await(); + assertThat(countDownListener.await()).isTrue(); assertThat(instance.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_COMPLETED); } From 4babc01d5c948dd4678bc108ff6eb0e7deaf20d9 Mon Sep 17 00:00:00 2001 From: Enrique Gonzalez Martinez Date: Thu, 31 Oct 2024 17:05:20 +0100 Subject: [PATCH 3/3] fix Composite rest --- .../workflow/instance/node/BoundaryEventNodeInstance.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/BoundaryEventNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/BoundaryEventNodeInstance.java index d8912fe7ea4..40e911c01be 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/BoundaryEventNodeInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/BoundaryEventNodeInstance.java @@ -24,6 +24,7 @@ import org.jbpm.ruleflow.core.Metadata; import org.jbpm.workflow.core.node.BoundaryEventNode; +import org.jbpm.workflow.instance.NodeInstance; import org.jbpm.workflow.instance.NodeInstanceContainer; import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl; import org.slf4j.Logger; @@ -43,7 +44,7 @@ public void signalEvent(String type, Object event, Function varR BoundaryEventNode boundaryNode = (BoundaryEventNode) getEventNode(); String attachedTo = boundaryNode.getAttachedToNodeId(); - Collection nodeInstances = getNodeInstanceContainer().getNodeInstances(); + Collection nodeInstances = getProcessInstance().getNodeInstances(true); if (type != null && type.startsWith(Metadata.EVENT_TYPE_COMPENSATION)) { // if not active && completed, signal if (!isAttachedToNodeActive(nodeInstances, attachedTo, type, event) && isAttachedToNodeCompleted(attachedTo)) { @@ -65,9 +66,9 @@ public void signalEvent(String type, Object event) { this.signalEvent(type, event, varName -> this.getVariable(varName)); } - private boolean isAttachedToNodeActive(Collection nodeInstances, String attachedTo, String type, Object event) { + private boolean isAttachedToNodeActive(Collection nodeInstances, String attachedTo, String type, Object event) { if (nodeInstances != null && !nodeInstances.isEmpty()) { - for (org.kie.api.runtime.process.NodeInstance nInstance : nodeInstances) { + for (NodeInstance nInstance : nodeInstances) { String nodeUniqueId = (String) nInstance.getNode().getUniqueId(); boolean isActivating = ((WorkflowProcessInstanceImpl) nInstance.getProcessInstance()).getActivatingNodeIds().contains(nodeUniqueId); if (attachedTo.equals(nodeUniqueId) && !isActivating) {