Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jbpm.bpmn2.xml.ProcessHandler Maven / Gradle / Ivy
/**
* Copyright 2010 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.
* You may obtain a copy of the License at
*
* 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.bpmn2.xml;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.drools.core.xml.BaseAbstractHandler;
import org.drools.core.xml.ExtensibleXmlParser;
import org.drools.core.xml.Handler;
import org.jbpm.bpmn2.core.Association;
import org.jbpm.bpmn2.core.DataStore;
import org.jbpm.bpmn2.core.Definitions;
import org.jbpm.bpmn2.core.Error;
import org.jbpm.bpmn2.core.Escalation;
import org.jbpm.bpmn2.core.Interface;
import org.jbpm.bpmn2.core.IntermediateLink;
import org.jbpm.bpmn2.core.ItemDefinition;
import org.jbpm.bpmn2.core.Lane;
import org.jbpm.bpmn2.core.Message;
import org.jbpm.bpmn2.core.SequenceFlow;
import org.jbpm.bpmn2.core.Signal;
import org.jbpm.compiler.xml.ProcessBuildData;
import org.jbpm.process.core.ContextContainer;
import org.jbpm.process.core.context.exception.ActionExceptionHandler;
import org.jbpm.process.core.context.exception.CompensationHandler;
import org.jbpm.process.core.context.exception.CompensationScope;
import org.jbpm.process.core.context.exception.ExceptionScope;
import org.jbpm.process.core.context.swimlane.Swimlane;
import org.jbpm.process.core.event.EventFilter;
import org.jbpm.process.core.event.EventTypeFilter;
import org.jbpm.process.core.timer.Timer;
import org.jbpm.process.instance.impl.CancelNodeInstanceAction;
import org.jbpm.ruleflow.core.RuleFlowProcess;
import org.jbpm.ruleflow.core.validation.RuleFlowProcessValidator;
import org.jbpm.workflow.core.Connection;
import org.jbpm.workflow.core.Constraint;
import org.jbpm.workflow.core.DroolsAction;
import org.jbpm.workflow.core.impl.ConnectionImpl;
import org.jbpm.workflow.core.impl.ConnectionRef;
import org.jbpm.workflow.core.impl.ConstraintImpl;
import org.jbpm.workflow.core.impl.DroolsConsequenceAction;
import org.jbpm.workflow.core.impl.ExtendedNodeImpl;
import org.jbpm.workflow.core.impl.NodeImpl;
import org.jbpm.workflow.core.node.ActionNode;
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.ConstraintTrigger;
import org.jbpm.workflow.core.node.EndNode;
import org.jbpm.workflow.core.node.EventNode;
import org.jbpm.workflow.core.node.EventSubProcessNode;
import org.jbpm.workflow.core.node.EventTrigger;
import org.jbpm.workflow.core.node.HumanTaskNode;
import org.jbpm.workflow.core.node.RuleSetNode;
import org.jbpm.workflow.core.node.Split;
import org.jbpm.workflow.core.node.StartNode;
import org.jbpm.workflow.core.node.StateBasedNode;
import org.jbpm.workflow.core.node.StateNode;
import org.jbpm.workflow.core.node.Trigger;
import org.jbpm.workflow.core.node.WorkItemNode;
import org.kie.api.definition.process.Node;
import org.kie.api.definition.process.NodeContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
public class ProcessHandler extends BaseAbstractHandler implements Handler {
private static final Logger logger = LoggerFactory.getLogger(ProcessHandler.class);
public static final String CONNECTIONS = "BPMN.Connections";
public static final String LINKS = "BPMN.ThrowLinks";
public static final String ASSOCIATIONS = "BPMN.Associations";
public static final String ERRORS = "BPMN.Errors";
public static final String ESCALATIONS = "BPMN.Escalations";
static final String PROCESS_INSTANCE_SIGNAL_EVENT = "kcontext.getProcessInstance().signalEvent(\"";
static final String RUNTIME_SIGNAL_EVENT = "kcontext.getKnowledgeRuntime().signalEvent(\"";
static final String RUNTIME_MANAGER_SIGNAL_EVENT = "((org.kie.api.runtime.manager.RuntimeManager)kcontext.getKnowledgeRuntime().getEnvironment().get(\"RuntimeManager\")).signalEvent(\"";
@SuppressWarnings("unchecked")
public ProcessHandler() {
if ((this.validParents == null) && (this.validPeers == null)) {
this.validParents = new HashSet();
this.validParents.add(Definitions.class);
this.validPeers = new HashSet();
this.validPeers.add(null);
this.validPeers.add(ItemDefinition.class);
this.validPeers.add(Message.class);
this.validPeers.add(Interface.class);
this.validPeers.add(Escalation.class);
this.validPeers.add(Error.class);
this.validPeers.add(Signal.class);
this.validPeers.add(DataStore.class);
this.validPeers.add(RuleFlowProcess.class);
this.allowNesting = false;
}
}
public Object start(final String uri, final String localName,
final Attributes attrs, final ExtensibleXmlParser parser)
throws SAXException {
parser.startElementBuilder(localName, attrs);
String id = attrs.getValue("id");
String name = attrs.getValue("name");
String packageName = attrs.getValue("http://www.jboss.org/drools", "packageName");
String dynamic = attrs.getValue("http://www.jboss.org/drools", "adHoc");
String version = attrs.getValue("http://www.jboss.org/drools", "version");
RuleFlowProcess process = new RuleFlowProcess();
process.setAutoComplete(true);
process.setId(id);
if (name == null) {
name = id;
}
process.setName(name);
process.setType("RuleFlow");
if (packageName == null) {
packageName = "org.drools.bpmn2";
}
process.setPackageName(packageName);
if ("true".equals(dynamic)) {
process.setDynamic(true);
process.setAutoComplete(false);
}
if (version != null) {
process.setVersion(version);
}
((ProcessBuildData) parser.getData()).addProcess(process);
// register the definitions object as metadata of process.
process.setMetaData("Definitions", parser.getParent());
// register bpmn2 imports as meta data of process
Object typedImports = ((ProcessBuildData) parser.getData()).getMetaData("Bpmn2Imports");
if (typedImports != null) {
process.setMetaData("Bpmn2Imports", typedImports);
}
// register item definitions as meta data of process
Object itemDefinitions = ((ProcessBuildData) parser.getData()).getMetaData("ItemDefinitions");
if (itemDefinitions != null) {
process.setMetaData("ItemDefinitions", itemDefinitions);
}
// for unique id's of nodes, start with one to avoid returning wrong nodes for dynamic nodes
parser.getMetaData().put("idGen", new AtomicInteger(1));
return process;
}
@SuppressWarnings("unchecked")
public Object end(final String uri, final String localName,
final ExtensibleXmlParser parser) throws SAXException {
parser.endElementBuilder();
RuleFlowProcess process = (RuleFlowProcess) parser.getCurrent();
List throwLinks = (List) process
.getMetaData(LINKS);
linkIntermediateLinks(process, throwLinks);
List connections = (List) process.getMetaData(CONNECTIONS);
linkConnections(process, connections);
linkBoundaryEvents(process);
// This must be done *after* linkConnections(process, connections)
// because it adds hidden connections for compensations
List associations = (List) process.getMetaData(ASSOCIATIONS);
linkAssociations((Definitions) process.getMetaData("Definitions"), process, associations);
List lanes = (List)
process.getMetaData(LaneHandler.LANES);
assignLanes(process, lanes);
postProcessNodes(process, process);
return process;
}
public static void linkIntermediateLinks(NodeContainer process,
List links) {
if (null != links) {
// Search throw links
ArrayList throwLinks = new ArrayList();
for (IntermediateLink aLinks : links) {
if (aLinks.isThrowLink()) {
throwLinks.add(aLinks);
}
}
// Look for catch links for a throw link
for (IntermediateLink throwLink : throwLinks) {
ArrayList linksWithSharedNames = new ArrayList();
for (IntermediateLink aLink : links) {
if (throwLink.getName().equals(aLink.getName())) {
linksWithSharedNames.add(aLink);
}
}
if (linksWithSharedNames.size() < 2) {
throw new IllegalArgumentException(
"There should be at least 2 link events to make a connection");
}
linksWithSharedNames.remove(throwLink);
// Make the connections
Node t = findNodeByIdOrUniqueIdInMetadata(process,
throwLink.getUniqueId());
// connect throw to catch
for (IntermediateLink catchLink : linksWithSharedNames) {
Node c = findNodeByIdOrUniqueIdInMetadata(process,
catchLink.getUniqueId());
if (t != null && c != null) {
Connection result = new ConnectionImpl(t,
NodeImpl.CONNECTION_DEFAULT_TYPE, c,
NodeImpl.CONNECTION_DEFAULT_TYPE);
result.setMetaData("linkNodeHidden", "yes");
}
}
// Remove processed links
links.remove(throwLink);
links.removeAll(linksWithSharedNames);
}
if (links.size() > 0) {
throw new IllegalArgumentException(links.size()
+ " links were not processed");
}
}
}
private static Object findNodeOrDataStoreByUniqueId(Definitions definitions, NodeContainer nodeContainer, final String nodeRef, String errorMsg) {
if( definitions != null ) {
List dataStores = definitions.getDataStores();
if( dataStores != null ) {
for( DataStore dataStore : dataStores ) {
if( nodeRef.equals(dataStore.getId()) ) {
return dataStore;
}
}
}
}
return findNodeByIdOrUniqueIdInMetadata(nodeContainer, nodeRef, errorMsg);
}
private static Node findNodeByIdOrUniqueIdInMetadata(
NodeContainer nodeContainer, String targetRef) {
return findNodeByIdOrUniqueIdInMetadata(nodeContainer, targetRef, "Could not find target node for connection:" + targetRef);
}
private static Node findNodeByIdOrUniqueIdInMetadata(NodeContainer nodeContainer, final String nodeRef, String errorMsg) {
Node node = null;
// try looking for a node with same "UniqueId" (in metadata)
for (Node containerNode: nodeContainer.getNodes()) {
if (nodeRef.equals(containerNode.getMetaData().get("UniqueId"))) {
node = containerNode;
break;
}
}
if (node == null) {
throw new IllegalArgumentException(errorMsg);
}
return node;
}
public Class> generateNodeFor() {
return RuleFlowProcess.class;
}
public static void linkConnections(NodeContainer nodeContainer, List connections) {
if (connections != null) {
for (SequenceFlow connection: connections) {
String sourceRef = connection.getSourceRef();
Node source = findNodeByIdOrUniqueIdInMetadata(nodeContainer, sourceRef, "Could not find source node for connection:" + sourceRef);
if (source instanceof EventNode) {
for (EventFilter eventFilter : ((EventNode) source).getEventFilters()) {
if (eventFilter instanceof EventTypeFilter) {
if ("Compensation".equals(((EventTypeFilter) eventFilter).getType())) {
// While this isn't explicitly stated in the spec,
// BPMN Method & Style, 2nd Ed. (Silver), states this on P. 131
throw new IllegalArgumentException(
"A Compensation Boundary Event can only be *associated* with a compensation activity via an Association, not via a Sequence Flow element.");
}
}
}
}
String targetRef = connection.getTargetRef();
Node target = findNodeByIdOrUniqueIdInMetadata(nodeContainer, targetRef, "Could not find target node for connection:" + targetRef);
Connection result = new ConnectionImpl(
source, NodeImpl.CONNECTION_DEFAULT_TYPE,
target, NodeImpl.CONNECTION_DEFAULT_TYPE);
result.setMetaData("bendpoints", connection.getBendpoints());
result.setMetaData("UniqueId", connection.getId());
if ("true".equals(System.getProperty("jbpm.enable.multi.con"))){
NodeImpl nodeImpl = (NodeImpl) source;
Constraint constraint = buildConstraint(connection, nodeImpl);
if (constraint != null) {
nodeImpl.addConstraint(new ConnectionRef(target.getId(), NodeImpl.CONNECTION_DEFAULT_TYPE),
constraint);
}
} else if (source instanceof Split) {
Split split = (Split) source;
Constraint constraint = buildConstraint(connection, split);
split.addConstraint(
new ConnectionRef(target.getId(), NodeImpl.CONNECTION_DEFAULT_TYPE),
constraint);
}
}
}
}
public static void linkBoundaryEvents(NodeContainer nodeContainer) {
for (Node node: nodeContainer.getNodes()) {
if (node instanceof EventNode) {
final String attachedTo = (String) node.getMetaData().get("AttachedTo");
if (attachedTo != null) {
for( EventFilter filter : ((EventNode) node).getEventFilters() ) {
String type = ((EventTypeFilter) filter).getType();
Node attachedNode = findNodeByIdOrUniqueIdInMetadata(nodeContainer, attachedTo, "Could not find node to attach to: " + attachedTo);
//
if (!(attachedNode instanceof StateBasedNode) && !type.equals("Compensation")) {
throw new IllegalArgumentException("Boundary events are supported only on StateBasedNode, found node: "
+ attachedNode.getClass().getName() + " [" + attachedNode.getMetaData().get("UniqueId") + "]");
}
if (type.startsWith("Escalation")) {
linkBoundaryEscalationEvent(nodeContainer, node, attachedTo, attachedNode);
} else if (type.startsWith("Error-")) {
linkBoundaryErrorEvent(nodeContainer, node, attachedTo, attachedNode);
} else if (type.startsWith("Timer-")) {
linkBoundaryTimerEvent(nodeContainer, node, attachedTo, attachedNode);
} else if (type.equals("Compensation")) {
linkBoundaryCompensationEvent(nodeContainer, node, attachedTo, attachedNode);
} else if (node.getMetaData().get("SignalName") != null || type.startsWith("Message-")) {
linkBoundarySignalEvent(nodeContainer, node, attachedTo, attachedNode);
} else if (type.startsWith("Condition-")) {
linkBoundaryConditionEvent(nodeContainer, node, attachedTo, attachedNode);
}
}
}
}
}
}
private static void linkBoundaryEscalationEvent(NodeContainer nodeContainer, Node node, String attachedTo, Node attachedNode) {
boolean cancelActivity = (Boolean) node.getMetaData().get("CancelActivity");
String escalationCode = (String) node.getMetaData().get("EscalationEvent");
String escalationStructureRef = (String) node.getMetaData().get("EscalationStructureRef");
ContextContainer compositeNode = (ContextContainer) attachedNode;
ExceptionScope exceptionScope = (ExceptionScope)
compositeNode.getDefaultContext(ExceptionScope.EXCEPTION_SCOPE);
if (exceptionScope == null) {
exceptionScope = new ExceptionScope();
compositeNode.addContext(exceptionScope);
compositeNode.setDefaultContext(exceptionScope);
}
String variable = ((EventNode)node).getVariableName();
ActionExceptionHandler exceptionHandler = new ActionExceptionHandler();
DroolsConsequenceAction action = new DroolsConsequenceAction("java",
PROCESS_INSTANCE_SIGNAL_EVENT + "Escalation-" + attachedTo + "-" + escalationCode + "\", kcontext.getVariable(\"" + variable +"\"));");
exceptionHandler.setAction(action);
exceptionHandler.setFaultVariable(variable);
exceptionScope.setExceptionHandler(escalationCode, exceptionHandler);
if (escalationStructureRef != null) {
exceptionScope.setExceptionHandler(escalationStructureRef, exceptionHandler);
}
if (cancelActivity) {
List actions = ((EventNode)node).getActions(EndNode.EVENT_NODE_EXIT);
if (actions == null) {
actions = new ArrayList();
}
DroolsConsequenceAction cancelAction = new DroolsConsequenceAction("java", null);
cancelAction.setMetaData("Action", new CancelNodeInstanceAction(attachedTo));
actions.add(cancelAction);
((EventNode)node).setActions(EndNode.EVENT_NODE_EXIT, actions);
}
}
private static void linkBoundaryErrorEvent(NodeContainer nodeContainer, Node node, String attachedTo, Node attachedNode) {
ContextContainer compositeNode = (ContextContainer) attachedNode;
ExceptionScope exceptionScope = (ExceptionScope)
compositeNode.getDefaultContext(ExceptionScope.EXCEPTION_SCOPE);
if (exceptionScope == null) {
exceptionScope = new ExceptionScope();
compositeNode.addContext(exceptionScope);
compositeNode.setDefaultContext(exceptionScope);
}
String errorCode = (String) node.getMetaData().get("ErrorEvent");
boolean hasErrorCode = (Boolean) node.getMetaData().get("HasErrorEvent");
String errorStructureRef = (String) node.getMetaData().get("ErrorStructureRef");
ActionExceptionHandler exceptionHandler = new ActionExceptionHandler();
String variable = ((EventNode)node).getVariableName();
DroolsConsequenceAction action = new DroolsConsequenceAction("java",
PROCESS_INSTANCE_SIGNAL_EVENT + "Error-" + attachedTo + "-" + errorCode + "\", kcontext.getVariable(\"" + variable +"\"));");
exceptionHandler.setAction(action);
exceptionHandler.setFaultVariable(variable);
exceptionScope.setExceptionHandler(hasErrorCode?errorCode:null, exceptionHandler);
if (errorStructureRef != null) {
exceptionScope.setExceptionHandler(errorStructureRef, exceptionHandler);
}
List actions = ((EventNode)node).getActions(EndNode.EVENT_NODE_EXIT);
if (actions == null) {
actions = new ArrayList();
}
DroolsConsequenceAction cancelAction = new DroolsConsequenceAction("java", null);
cancelAction.setMetaData("Action", new CancelNodeInstanceAction(attachedTo));
actions.add(cancelAction);
((EventNode)node).setActions(EndNode.EVENT_NODE_EXIT, actions);
}
private static void linkBoundaryTimerEvent(NodeContainer nodeContainer, Node node, String attachedTo, Node attachedNode) {
boolean cancelActivity = (Boolean) node.getMetaData().get("CancelActivity");
StateBasedNode compositeNode = (StateBasedNode) attachedNode;
String timeDuration = (String) node.getMetaData().get("TimeDuration");
String timeCycle = (String) node.getMetaData().get("TimeCycle");
String timeDate = (String) node.getMetaData().get("TimeDate");
Timer timer = new Timer();
if (timeDuration != null) {
timer.setDelay(timeDuration);
timer.setTimeType(Timer.TIME_DURATION);
compositeNode.addTimer(timer, new DroolsConsequenceAction("java",
PROCESS_INSTANCE_SIGNAL_EVENT + "Timer-" + attachedTo + "-" + timeDuration + "\", kcontext.getNodeInstance().getId());"));
} else if (timeCycle != null) {
int index = timeCycle.indexOf("###");
if (index != -1) {
String period = timeCycle.substring(index + 3);
timeCycle = timeCycle.substring(0, index);
timer.setPeriod(period);
}
timer.setDelay(timeCycle);
timer.setTimeType(Timer.TIME_CYCLE);
compositeNode.addTimer(timer, new DroolsConsequenceAction("java",
PROCESS_INSTANCE_SIGNAL_EVENT + "Timer-" + attachedTo + "-" + timeCycle + (timer.getPeriod() == null ? "" : "###" + timer.getPeriod()) + "\", kcontext.getNodeInstance().getId());"));
} else if (timeDate != null) {
timer.setDate(timeDate);
timer.setTimeType(Timer.TIME_DATE);
compositeNode.addTimer(timer, new DroolsConsequenceAction("java",
PROCESS_INSTANCE_SIGNAL_EVENT + "Timer-" + attachedTo + "-" + timeDate + "\", kcontext.getNodeInstance().getId());"));
}
if (cancelActivity) {
List actions = ((EventNode)node).getActions(EndNode.EVENT_NODE_EXIT);
if (actions == null) {
actions = new ArrayList();
}
DroolsConsequenceAction cancelAction = new DroolsConsequenceAction("java", null);
cancelAction.setMetaData("Action", new CancelNodeInstanceAction(attachedTo));
actions.add(cancelAction);
((EventNode)node).setActions(EndNode.EVENT_NODE_EXIT, actions);
}
}
private static void linkBoundaryCompensationEvent(NodeContainer nodeContainer, Node node, String attachedTo, Node attachedNode) {
/**
* BPMN2 Spec, p. 264:
* "For an Intermediate event attached to the boundary of an activity:"
* ...
* The Activity the Event is attached to will provide the Id necessary
* to match the Compensation Event with the Event that threw the compensation"
*
* In other words: "activityRef" is and should be IGNORED
*/
String activityRef = (String) node.getMetaData().get("ActivityRef");
if( activityRef != null ) {
logger.warn("Attribute activityRef={} will be IGNORED since this is a Boundary Compensation Event.", activityRef);
}
// linkAssociations takes care of the rest
}
private static void linkBoundarySignalEvent(NodeContainer nodeContainer, Node node, String attachedTo, Node attachedNode) {
boolean cancelActivity = (Boolean) node.getMetaData().get("CancelActivity");
if (cancelActivity) {
List actions = ((EventNode)node).getActions(EndNode.EVENT_NODE_EXIT);
if (actions == null) {
actions = new ArrayList();
}
DroolsConsequenceAction action = new DroolsConsequenceAction("java", null);
action.setMetaData("Action", new CancelNodeInstanceAction(attachedTo));
actions.add(action);
((EventNode)node).setActions(EndNode.EVENT_NODE_EXIT, actions);
}
}
private static void linkBoundaryConditionEvent(NodeContainer nodeContainer, Node node, String attachedTo, Node attachedNode) {
String processId = ((RuleFlowProcess) nodeContainer).getId();
String eventType = "RuleFlowStateEvent-" + processId + "-" + ((EventNode) node).getUniqueId() + "-" + attachedTo;
((EventTypeFilter) ((EventNode) node).getEventFilters().get(0)).setType(eventType);
boolean cancelActivity = (Boolean) node.getMetaData().get("CancelActivity");
if (cancelActivity) {
List actions = ((EventNode)node).getActions(EndNode.EVENT_NODE_EXIT);
if (actions == null) {
actions = new ArrayList();
}
DroolsConsequenceAction action = new DroolsConsequenceAction("java", null);
action.setMetaData("Action", new CancelNodeInstanceAction(attachedTo));
actions.add(action);
((EventNode)node).setActions(EndNode.EVENT_NODE_EXIT, actions);
}
}
public static void linkAssociations(Definitions definitions, NodeContainer nodeContainer, List associations) {
if( associations != null ) {
for( Association association : associations ) {
String sourceRef = association.getSourceRef();
Object source = null;
try {
source = findNodeOrDataStoreByUniqueId(definitions, nodeContainer, sourceRef,
"Could not find source [" + sourceRef + "] for association " + association.getId() + "]" );
} catch (IllegalArgumentException e) {
// source not found
}
String targetRef = association.getTargetRef();
Object target = null;
try {
target = findNodeOrDataStoreByUniqueId(definitions, nodeContainer, targetRef,
"Could not find target [" + targetRef + "] for association [" + association.getId() + "]" );
} catch (IllegalArgumentException e) {
// target not found
}
if (source == null || target == null) {
// TODO: ignoring this association for now
} else if (target instanceof DataStore || source instanceof DataStore) {
// TODO: ignoring data store associations for now
} else if (source instanceof EventNode) {
EventNode sourceNode = (EventNode) source;
Node targetNode = (Node) target;
checkBoundaryEventCompensationHandler(association, sourceNode, targetNode);
// make sure IsForCompensation is set to true on target
NodeImpl targetNodeImpl = (NodeImpl) target;
String isForCompensation = "isForCompensation";
Object compensationObject = targetNodeImpl.getMetaData(isForCompensation);
if( compensationObject == null ) {
targetNodeImpl.setMetaData(isForCompensation, true);
logger.warn("Setting {} attribute to true for node {}", isForCompensation, targetRef );
} else if( ! Boolean.parseBoolean(compensationObject.toString()) ) {
throw new IllegalArgumentException(isForCompensation + " attribute [" + compensationObject + "] should be true for Compensation Activity [" + targetRef + "]");
}
// put Compensation Handler in CompensationHandlerNode
NodeContainer sourceParent = sourceNode.getNodeContainer();
NodeContainer targetParent = targetNode.getNodeContainer();
if( ! sourceParent.equals(targetParent) ) {
throw new IllegalArgumentException("Compensation Associations may not cross (sub-)process boundaries,");
}
// connect boundary event to compensation activity
ConnectionImpl connection = new ConnectionImpl(sourceNode, NodeImpl.CONNECTION_DEFAULT_TYPE, targetNode, NodeImpl.CONNECTION_DEFAULT_TYPE);
connection.setMetaData("UniqueId", null);
connection.setMetaData("hidden", true );
connection.setMetaData("association", true );
// Compensation use cases:
// - boundary event --associated-> activity
// - implicit sub process compensation handler + recursive?
/**
* BPMN2 spec, p.442:
* "A Compensation Event Sub-process becomes enabled when its parent Activity transitions into state
* Completed. At that time, a snapshot of the data associated with the parent Acitivity is taken and kept for
* later usage by the Compensation Event Sub-Process."
*/
}
}
}
}
/**
* This logic belongs in {@link RuleFlowProcessValidator} -- except that {@link Association}s are a jbpm-bpmn2 class,
* and {@link RuleFlowProcessValidator} is a jbpm-flow class..
*
* Maybe we should have a BPMNProcessValidator class?
*
* @param association The association to check.
* @param source The source of the association.
* @param target The target of the association.
*/
private static void checkBoundaryEventCompensationHandler(Association association, Node source, Node target) {
// check that
// - event node is boundary event node
if( ! (source instanceof BoundaryEventNode) ) {
throw new IllegalArgumentException("(Compensation) activities may only be associated with Boundary Event Nodes (not with" +
source.getClass().getSimpleName() + " nodes [node " + ((String) source.getMetaData().get("UniqueId")) + "].");
}
BoundaryEventNode eventNode = (BoundaryEventNode) source;
// - event node has compensationEvent
List eventFilters = eventNode.getEventFilters();
boolean compensationCheckPassed = false;
if( eventFilters != null) {
for( EventFilter filter : eventFilters ) {
if( filter instanceof EventTypeFilter ) {
String type = ((EventTypeFilter) filter).getType();
if( type != null && type.equals("Compensation") ) {
compensationCheckPassed = true;
}
}
}
}
if( ! compensationCheckPassed ) {
throw new IllegalArgumentException("An Event [" + ((String) eventNode.getMetaData("UniqueId"))
+ "] linked from an association [" + association.getId()
+ "] must be a (Boundary) Compensation Event.");
}
// - boundary event node is attached to the correct type of node?
/**
* Tasks:
* business: RuleSetNode
* manual: WorkItemNode
* receive: WorkItemNode
* script: ActionNode
* send: WorkItemNode
* service: WorkItemNode
* task: WorkItemNode
* user: HumanTaskNode
*/
String attachedToId = eventNode.getAttachedToNodeId();
Node attachedToNode = null;
for( Node node : eventNode.getNodeContainer().getNodes() ) {
if( attachedToId.equals(node.getMetaData().get("UniqueId")) ) {
attachedToNode = node;
break;
}
}
if( attachedToNode == null ) {
throw new IllegalArgumentException("Boundary Event [" + ((String) eventNode.getMetaData("UniqueId"))
+ "] is not attached to a node [" + attachedToId + "] that can be found.");
}
if( !( attachedToNode instanceof RuleSetNode
|| attachedToNode instanceof WorkItemNode
|| attachedToNode instanceof ActionNode
|| attachedToNode instanceof HumanTaskNode
|| attachedToNode instanceof CompositeNode) ) {
throw new IllegalArgumentException("Compensation Boundary Event [" + ((String) eventNode.getMetaData("UniqueId"))
+ "] must be attached to a task or sub-process.");
}
// - associated node is a task or subProcess
compensationCheckPassed = false;
if( target instanceof WorkItemNode || target instanceof HumanTaskNode
|| target instanceof CompositeContextNode ) {
compensationCheckPassed = true;
} else if( target instanceof ActionNode ) {
Object nodeTypeObj = ((ActionNode) target).getMetaData("NodeType");
if( nodeTypeObj != null && nodeTypeObj.equals("ScriptTask") ) {
compensationCheckPassed = true;
}
}
if( ! compensationCheckPassed ) {
throw new IllegalArgumentException("An Activity ["
+ ((String) ((NodeImpl)target).getMetaData("UniqueId")) +
"] associated with a Boundary Compensation Event must be a Task or a (non-Event) Sub-Process");
}
// - associated node does not have outgoingConnections of it's own
compensationCheckPassed = true;
NodeImpl targetNode = (NodeImpl) target;
Map> connectionsMap = targetNode.getOutgoingConnections();
ConnectionImpl outgoingConnection = null;
for( String connectionType : connectionsMap.keySet() ) {
List connections = connectionsMap.get(connectionType);
if( connections != null && ! connections.isEmpty() ) {
for( org.kie.api.definition.process.Connection connection : connections ) {
Object hiddenObj = connection.getMetaData().get("hidden");
if( hiddenObj != null && ((Boolean) hiddenObj) ) {
continue;
}
outgoingConnection = (ConnectionImpl) connection;
compensationCheckPassed = false;
break;
}
}
}
if( ! compensationCheckPassed ) {
throw new IllegalArgumentException("A Compensation Activity ["
+ ((String) targetNode.getMetaData("UniqueId"))
+ "] may not have any outgoing connection ["
+ (String) outgoingConnection.getMetaData("UniqueId") + "]");
}
}
private void assignLanes(RuleFlowProcess process, List lanes) {
List laneNames = new ArrayList();
Map laneMapping = new HashMap();
if (lanes != null) {
for (Lane lane: lanes) {
String name = lane.getName();
if (name != null) {
Swimlane swimlane = new Swimlane();
swimlane.setName(name);
process.getSwimlaneContext().addSwimlane(swimlane);
laneNames.add(name);
for (String flowElementRef: lane.getFlowElements()) {
laneMapping.put(flowElementRef, name);
}
}
}
}
assignLanes(process, laneMapping);
}
private void postProcessNodes(RuleFlowProcess process, NodeContainer container) {
for (Node node: container.getNodes()) {
if (node instanceof StateNode) {
StateNode stateNode = (StateNode) node;
String condition = (String) stateNode.getMetaData("Condition");
Constraint constraint = new ConstraintImpl();
constraint.setConstraint(condition);
constraint.setType("rule");
for (org.kie.api.definition.process.Connection connection: stateNode.getDefaultOutgoingConnections()) {
stateNode.setConstraint(connection, constraint);
}
} else if (node instanceof NodeContainer) {
// prepare event sub process
if (node instanceof EventSubProcessNode) {
EventSubProcessNode eventSubProcessNode = (EventSubProcessNode) node;
Node[] nodes = eventSubProcessNode.getNodes();
for (Node subNode : nodes) {
// avoids cyclomatic complexity
if (subNode == null || ! (subNode instanceof StartNode)) {
continue;
}
List triggers = ((StartNode) subNode).getTriggers();
if ( triggers == null ) {
continue;
}
for ( Trigger trigger : triggers ) {
if ( trigger instanceof EventTrigger ) {
final List filters = ((EventTrigger) trigger).getEventFilters();
for ( EventFilter filter : filters ) {
if ( filter instanceof EventTypeFilter ) {
eventSubProcessNode.addEvent((EventTypeFilter) filter);
String type = ((EventTypeFilter) filter).getType();
if (type.startsWith("Error-") || type.startsWith("Escalation")) {
String faultCode = (String) subNode.getMetaData().get("FaultCode");
String replaceRegExp = "Error-|Escalation-";
final String signalType = type;
ExceptionScope exceptionScope = (ExceptionScope) ((ContextContainer) eventSubProcessNode.getNodeContainer()).getDefaultContext(ExceptionScope.EXCEPTION_SCOPE);
if (exceptionScope == null) {
exceptionScope = new ExceptionScope();
((ContextContainer) eventSubProcessNode.getNodeContainer()).addContext(exceptionScope);
((ContextContainer) eventSubProcessNode.getNodeContainer()).setDefaultContext(exceptionScope);
}
String faultVariable = null;
if (trigger.getInAssociations() != null && !trigger.getInAssociations().isEmpty()) {
faultVariable = trigger.getInAssociations().get(0).getTarget();
}
ActionExceptionHandler exceptionHandler = new ActionExceptionHandler();
DroolsConsequenceAction action = new DroolsConsequenceAction("java", PROCESS_INSTANCE_SIGNAL_EVENT + signalType+"\", "
+(faultVariable==null?"null":"kcontext.getVariable(\""+faultVariable+"\")")+");");
exceptionHandler.setAction(action);
exceptionHandler.setFaultVariable(faultVariable);
if (faultCode != null) {
exceptionScope.setExceptionHandler(type.replaceFirst(replaceRegExp, ""), exceptionHandler);
} else {
exceptionScope.setExceptionHandler(faultCode, exceptionHandler);
}
} else if( type.equals("Compensation") ) {
// 1. Find the parent sub-process to this event sub-process
NodeContainer parentSubProcess;
NodeContainer subProcess = eventSubProcessNode.getNodeContainer();
Object isForCompensationObj = eventSubProcessNode.getMetaData("isForCompensation");
if( isForCompensationObj == null ) {
eventSubProcessNode.setMetaData("isForCompensation", true );
logger.warn( "Overriding empty or false value of \"isForCompensation\" attribute on Event Sub-Process ["
+ eventSubProcessNode.getMetaData("UniqueId") + "] and setting it to true.");
}
if( subProcess instanceof RuleFlowProcess ) {
// If jBPM deletes the process (instance) as soon as the process completes..
// ..how do you expect to signal compensation on the completed process (instance)?!?
throw new IllegalArgumentException("Compensation Event Sub-Processes at the process level are not supported.");
}
parentSubProcess = ((Node) subProcess).getNodeContainer();
// 2. The event filter (never fires, purely for dumping purposes) has already been added
// 3. Add compensation scope
String compensationHandlerId = (String) ((CompositeNode) subProcess).getMetaData("UniqueId");
addCompensationScope(process, eventSubProcessNode, parentSubProcess, compensationHandlerId);
}
}
}
} else if (trigger instanceof ConstraintTrigger) {
ConstraintTrigger constraintTrigger = (ConstraintTrigger) trigger;
if (constraintTrigger.getConstraint() != null) {
String processId = ((RuleFlowProcess) container).getId();
String type = "RuleFlowStateEventSubProcess-" + processId + "-" + eventSubProcessNode.getUniqueId();
EventTypeFilter eventTypeFilter = new EventTypeFilter();
eventTypeFilter.setType(type);
eventSubProcessNode.addEvent(eventTypeFilter);
}
}
}
} // for( Node subNode : nodes)
}
postProcessNodes(process, (NodeContainer) node);
} else if( node instanceof EndNode ) {
handleIntermediateOrEndThrowCompensationEvent((EndNode) node);
} else if( node instanceof ActionNode ) {
handleIntermediateOrEndThrowCompensationEvent((ActionNode) node);
} else if( node instanceof EventNode ) {
final EventNode eventNode = (EventNode) node;
if (!(eventNode instanceof BoundaryEventNode) && eventNode.getDefaultIncomingConnections().size() == 0) {
throw new IllegalArgumentException("Event node '" + node.getName() + "' [" + node.getId() + "] has no incoming connection");
}
}
}
}
private void assignLanes(NodeContainer nodeContainer, Map laneMapping) {
for (Node node: nodeContainer.getNodes()) {
String lane = null;
String uniqueId = (String) node.getMetaData().get("UniqueId");
if (uniqueId != null) {
lane = laneMapping.get(uniqueId);
} else {
lane = laneMapping.get(XmlBPMNProcessDumper.getUniqueNodeId(node));
}
if (lane != null) {
((NodeImpl) node).setMetaData("Lane", lane);
if (node instanceof HumanTaskNode) {
((HumanTaskNode) node).setSwimlane(lane);
}
}
if (node instanceof NodeContainer) {
assignLanes((NodeContainer) node, laneMapping);
}
}
}
private static Constraint buildConstraint(SequenceFlow connection, NodeImpl node) {
if (connection.getExpression() == null) {
return null;
}
Constraint constraint = new ConstraintImpl();
String defaultConnection = (String) node.getMetaData("Default");
if (defaultConnection != null && defaultConnection.equals(connection.getId())) {
constraint.setDefault(true);
}
if (connection.getName() != null) {
constraint.setName(connection.getName());
} else {
constraint.setName("");
}
if (connection.getType() != null) {
constraint.setType(connection.getType());
} else {
constraint.setType("code");
}
if (connection.getLanguage() != null) {
constraint.setDialect(connection.getLanguage());
}
if (connection.getExpression() != null) {
constraint.setConstraint(connection.getExpression());
}
constraint.setPriority(connection.getPriority());
return constraint;
}
protected static void addCompensationScope(final RuleFlowProcess process, final Node node,
final org.kie.api.definition.process.NodeContainer parentContainer, final String compensationHandlerId) {
process.getMetaData().put("Compensation", true);
assert parentContainer instanceof ContextContainer
: "Expected parent node to be a CompositeContextNode, not a " + parentContainer.getClass().getSimpleName();
ContextContainer contextContainer = (ContextContainer) parentContainer;
CompensationScope scope = null;
boolean addScope = false;
if (contextContainer.getContexts(CompensationScope.COMPENSATION_SCOPE) == null) {
addScope = true;
} else {
scope = (CompensationScope) contextContainer.getContexts(CompensationScope.COMPENSATION_SCOPE).get(0);
if (scope == null) {
addScope = true;
}
}
if (addScope) {
scope = new CompensationScope();
contextContainer.addContext(scope);
contextContainer.setDefaultContext(scope);
scope.setContextContainer(contextContainer);
}
CompensationHandler handler = new CompensationHandler();
handler.setNode(node);
if( scope.getExceptionHandler(compensationHandlerId) != null ) {
throw new IllegalArgumentException(
"More than one compensation handler per node (" + compensationHandlerId + ")" + " is not supported!");
}
scope.setExceptionHandler(compensationHandlerId, handler);
}
protected void handleIntermediateOrEndThrowCompensationEvent(ExtendedNodeImpl throwEventNode ) {
if( throwEventNode.getMetaData("compensation-activityRef") != null ) {
String activityRef = (String) throwEventNode.getMetaData().remove("compensation-activityRef");
NodeContainer nodeParent = (NodeContainer) throwEventNode.getNodeContainer();
if( nodeParent instanceof EventSubProcessNode ) {
boolean compensationEventSubProcess = false;
List startTriggers = ((EventSubProcessNode) nodeParent).findStartNode().getTriggers();
CESP_CHECK: for( Trigger trigger : startTriggers ) {
if( trigger instanceof EventTrigger ) {
for( EventFilter filter : ((EventTrigger) trigger).getEventFilters() ) {
if( ((EventTypeFilter) filter).getType().equals("Compensation") ) {
compensationEventSubProcess = true;
break CESP_CHECK;
}
}
}
}
if( compensationEventSubProcess ) {
// BPMN2 spec, p. 252, p. 248: intermediate and end compensation event visibility scope
nodeParent = (NodeContainer) ((NodeImpl) nodeParent).getNodeContainer();
}
}
String parentId;
if( nodeParent instanceof RuleFlowProcess ) {
parentId = ((RuleFlowProcess) nodeParent).getId();
} else {
parentId = (String) ((NodeImpl) nodeParent).getMetaData("UniqueId");
}
String compensationEvent;
if( activityRef.length() == 0 ) {
// general/implicit compensation
compensationEvent = CompensationScope.IMPLICIT_COMPENSATION_PREFIX + parentId;
} else {
// specific compensation
compensationEvent = activityRef;
}
DroolsConsequenceAction compensationAction = new DroolsConsequenceAction("java",
PROCESS_INSTANCE_SIGNAL_EVENT + "Compensation\", \"" + compensationEvent + "\");");
if( throwEventNode instanceof ActionNode ) {
((ActionNode) throwEventNode).setAction(compensationAction);
} else if( throwEventNode instanceof EndNode ) {
List actions = new ArrayList();
actions.add(compensationAction);
((EndNode) throwEventNode).setActions(EndNode.EVENT_NODE_ENTER, actions);
}
}
}
}