/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jbpm.workflow.instance.impl;
import static org.jbpm.process.core.context.exception.CompensationScope.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
import org.jbpm.process.core.ContextContainer;
import org.jbpm.process.core.context.exception.CompensationScope;
import org.jbpm.process.instance.ContextInstanceContainer;
import org.jbpm.process.instance.ProcessInstance;
import org.jbpm.process.instance.context.exception.CompensationScopeInstance;
import org.jbpm.ruleflow.core.RuleFlowProcess;
import org.jbpm.workflow.core.impl.NodeImpl;
import org.jbpm.workflow.instance.NodeInstance;
import org.jbpm.workflow.instance.NodeInstanceContainer;
import org.jbpm.workflow.instance.WorkflowRuntimeException;
import org.jbpm.workflow.instance.node.CompositeContextNodeInstance;
import org.jbpm.workflow.instance.node.CompositeNodeInstance;
import org.kie.api.definition.process.Node;
import org.kie.api.definition.process.NodeContainer;
import org.kie.api.definition.process.Process;
import org.kie.api.runtime.process.EventListener;
class CompensationEventListener implements EventListener {
private WorkflowProcessInstanceImpl instance;
public CompensationEventListener(WorkflowProcessInstanceImpl instance) {
this.instance = instance;
}
private ProcessInstance getProcessInstance() {
return instance;
}
/**
* When signaling compensation, you can do that in 1 of 2 ways:
* 1. signalEvent("Compensation", <node-with-compensation-handler-id>)
* This is specific compensation, that only possibly triggers the compensation handler
* attached to the node referred to by the <node-with-compensation-handler-id>.
* 2. signalEvent("Compensation", "implicit:" + <node-container-containing-compensation-scope-id> )
* This is implicit or general compensation, in which you trigger all visible compensation handlers
* (in the proper order, etc.) in the (sub-)process referred to by
* the <node-container-containing-compensation-scope-id>.
*/
public void signalEvent(String compensationType, Object activityRefStr) {
if( activityRefStr == null || ! (activityRefStr instanceof String) ) {
throw new WorkflowRuntimeException(null, getProcessInstance(),
"Compensation can only be triggered with String events, not an event of type "
+ activityRefStr == null ? "null" : activityRefStr.getClass().getSimpleName());
}
// 1. parse the activity ref (is it general or specific compensation?)
String activityRef = (String) activityRefStr;
String toCompensateNodeId = activityRef;
boolean generalCompensation = false;
if( activityRef.startsWith(IMPLICIT_COMPENSATION_PREFIX) ) {
toCompensateNodeId = activityRef.substring(IMPLICIT_COMPENSATION_PREFIX.length());
generalCompensation = true;
}
org.jbpm.process.core.Process process = (org.jbpm.process.core.Process) instance.getProcess();
// 2. for specific compensation: find the node that will be compensated
// for general compensation: find the compensation scope container that contains all the visible compensation handlers
Node toCompensateNode = null;
ContextContainer compensationScopeContainer = null;
if( generalCompensation ) {
if( toCompensateNodeId.equals(instance.getProcessId()) ) {
compensationScopeContainer = process;
} else {
compensationScopeContainer = (ContextContainer) findNode(toCompensateNodeId);
}
} else {
toCompensateNode = findNode(toCompensateNodeId);
}
// 3. If the node exists,
// a. find the node container for which the compensation handler is visible
// b. create the compensation scope instance
// c. handle the exception (which also cleans up the generated node instances)
if( toCompensateNode != null || compensationScopeContainer != null ) {
CompensationScope compensationScope = null;
if( compensationScopeContainer != null ) {
compensationScope = (CompensationScope) compensationScopeContainer.getDefaultContext(COMPENSATION_SCOPE);
} else {
compensationScope
= (CompensationScope) ((NodeImpl) toCompensateNode).resolveContext(COMPENSATION_SCOPE, toCompensateNodeId);
}
assert compensationScope != null : "Compensation scope for node [" + toCompensateNodeId + "] could not be found!";
CompensationScopeInstance scopeInstance;
if( compensationScope.getContextContainerId().equals(process.getId()) ) {
// process level compensation
scopeInstance = (CompensationScopeInstance) instance.getContextInstance(compensationScope);
} else {
// nested compensation
Stack<NodeInstance> generatedInstances;
if( toCompensateNode == null ) {
// logic is the same if it's specific or general
generatedInstances = createNodeInstanceContainers((Node) compensationScopeContainer, true);
} else {
generatedInstances = createNodeInstanceContainers(toCompensateNode, false);
}
NodeInstance nodeInstanceContainer = generatedInstances.peek();
scopeInstance
= ((CompensationScopeInstance)
((ContextInstanceContainer) nodeInstanceContainer).getContextInstance(compensationScope));
scopeInstance.addCompensationInstances(generatedInstances);
}
scopeInstance.handleException(activityRef, null);
}
}
private Node findNode(String nodeId) {
Node found = null;
Queue<Node> allProcessNodes = new LinkedList<Node>();
allProcessNodes.addAll(Arrays.asList( instance.getNodeContainer().getNodes() ));
while( ! allProcessNodes.isEmpty() ) {
Node node = allProcessNodes.poll();
if( nodeId.equals(node.getMetaData().get("UniqueId")) ) {
found = node;
break;
}
if( node instanceof NodeContainer ) {
allProcessNodes.addAll(Arrays.asList( ((NodeContainer) node).getNodes()));
}
}
return found;
}
private Stack<NodeInstance> createNodeInstanceContainers(Node toCompensateNode, boolean generalCompensation) {
Stack<NodeContainer> nestedNodes = new Stack<NodeContainer>();
Stack<NodeInstance> generatedInstances = new Stack<NodeInstance>();
NodeContainer parentContainer = toCompensateNode.getNodeContainer();
while( !(parentContainer instanceof RuleFlowProcess) ) {
nestedNodes.add(parentContainer);
parentContainer = ((Node) parentContainer).getNodeContainer();
}
NodeInstanceContainer parentInstance;
if( nestedNodes.isEmpty() ) {
// nestedNodes is empty
parentInstance = (NodeInstanceContainer) getProcessInstance();
} else {
parentInstance = (NodeInstanceContainer) ((WorkflowProcessInstanceImpl) getProcessInstance()).getNodeInstance((Node) nestedNodes.pop());
generatedInstances.add((NodeInstance) parentInstance);
}
NodeInstanceContainer childInstance = parentInstance;
while( ! nestedNodes.isEmpty() ) {
// generate
childInstance = (NodeInstanceContainer) parentInstance.getNodeInstance((Node) nestedNodes.pop());
assert childInstance instanceof CompositeNodeInstance
: "A node with child nodes should end up creating a CompositeNodeInstance type.";
// track and modify
generatedInstances.add((NodeInstance) childInstance);
// loop
parentInstance = (CompositeContextNodeInstance) childInstance;
}
if( generalCompensation ) {
childInstance = (NodeInstanceContainer) parentInstance.getNodeInstance(toCompensateNode);
generatedInstances.add((NodeInstance) childInstance);
}
return generatedInstances;
}
private final String [] eventTypes = { "Compensation" };
public String[] getEventTypes() {
return eventTypes;
}
}