/* 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.activiti.engine.impl.bpmn.parser;
import java.io.InputStream;
import java.net.URL;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.delegate.ExecutionListener;
import org.activiti.engine.delegate.Expression;
import org.activiti.engine.delegate.TaskListener;
import org.activiti.engine.impl.Condition;
import org.activiti.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.BoundaryEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.BusinessRuleTaskActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.CallActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.CancelBoundaryEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.CancelEndEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ErrorEndEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.EventBasedGatewayActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.EventSubProcessStartEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.InclusiveGatewayActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.IntermediateCatchEventActivitiBehaviour;
import org.activiti.engine.impl.bpmn.behavior.IntermediateThrowCompensationEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.IntermediateThrowNoneEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.IntermediateThrowSignalEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.MailActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ManualTaskActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.NoneEndEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.NoneStartEventActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ParallelGatewayActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.activiti.engine.impl.bpmn.behavior.ReceiveTaskActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ScriptTaskActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.activiti.engine.impl.bpmn.behavior.ServiceTaskDelegateExpressionActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ServiceTaskExpressionActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ShellActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.SubProcessActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.TaskActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.TransactionActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.WebServiceActivityBehavior;
import org.activiti.engine.impl.bpmn.data.AbstractDataAssociation;
import org.activiti.engine.impl.bpmn.data.Assignment;
import org.activiti.engine.impl.bpmn.data.ClassStructureDefinition;
import org.activiti.engine.impl.bpmn.data.Data;
import org.activiti.engine.impl.bpmn.data.DataRef;
import org.activiti.engine.impl.bpmn.data.IOSpecification;
import org.activiti.engine.impl.bpmn.data.ItemDefinition;
import org.activiti.engine.impl.bpmn.data.ItemKind;
import org.activiti.engine.impl.bpmn.data.SimpleDataInputAssociation;
import org.activiti.engine.impl.bpmn.data.StructureDefinition;
import org.activiti.engine.impl.bpmn.data.TransformationDataOutputAssociation;
import org.activiti.engine.impl.bpmn.helper.ClassDelegate;
import org.activiti.engine.impl.bpmn.listener.DelegateExpressionExecutionListener;
import org.activiti.engine.impl.bpmn.listener.DelegateExpressionTaskListener;
import org.activiti.engine.impl.bpmn.listener.ExpressionExecutionListener;
import org.activiti.engine.impl.bpmn.listener.ExpressionTaskListener;
import org.activiti.engine.impl.bpmn.webservice.BpmnInterface;
import org.activiti.engine.impl.bpmn.webservice.BpmnInterfaceImplementation;
import org.activiti.engine.impl.bpmn.webservice.MessageDefinition;
import org.activiti.engine.impl.bpmn.webservice.MessageImplicitDataInputAssociation;
import org.activiti.engine.impl.bpmn.webservice.MessageImplicitDataOutputAssociation;
import org.activiti.engine.impl.bpmn.webservice.Operation;
import org.activiti.engine.impl.bpmn.webservice.OperationImplementation;
import org.activiti.engine.impl.el.ExpressionManager;
import org.activiti.engine.impl.el.FixedValue;
import org.activiti.engine.impl.el.UelExpressionCondition;
import org.activiti.engine.impl.form.DefaultStartFormHandler;
import org.activiti.engine.impl.form.DefaultTaskFormHandler;
import org.activiti.engine.impl.form.StartFormHandler;
import org.activiti.engine.impl.form.TaskFormHandler;
import org.activiti.engine.impl.jobexecutor.TimerCatchIntermediateEventJobHandler;
import org.activiti.engine.impl.jobexecutor.TimerDeclarationImpl;
import org.activiti.engine.impl.jobexecutor.TimerDeclarationType;
import org.activiti.engine.impl.jobexecutor.TimerExecuteNestedActivityJobHandler;
import org.activiti.engine.impl.jobexecutor.TimerStartEventJobHandler;
import org.activiti.engine.impl.persistence.entity.DeploymentEntity;
import org.activiti.engine.impl.persistence.entity.JobEntity;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.delegate.ActivityBehavior;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.pvm.process.HasDIBounds;
import org.activiti.engine.impl.pvm.process.Lane;
import org.activiti.engine.impl.pvm.process.LaneSet;
import org.activiti.engine.impl.pvm.process.ParticipantProcess;
import org.activiti.engine.impl.pvm.process.ProcessDefinitionImpl;
import org.activiti.engine.impl.pvm.process.ScopeImpl;
import org.activiti.engine.impl.pvm.process.TransitionImpl;
import org.activiti.engine.impl.scripting.ScriptingEngines;
import org.activiti.engine.impl.task.TaskDefinition;
import org.activiti.engine.impl.util.ReflectUtil;
import org.activiti.engine.impl.util.xml.Element;
import org.activiti.engine.impl.util.xml.Parse;
import org.activiti.engine.impl.variable.VariableDeclaration;
import org.activiti.engine.repository.ProcessDefinition;
/**
* Specific parsing of one BPMN 2.0 XML file, created by the {@link BpmnParser}.
*
* @author Tom Baeyens
* @author Joram Barrez
* @author Christian Stettler
* @author Frederik Heremans
* @author Falko Menge
* @author Esteban Robles
* @author Daniel Meyer
* @author Saeid Mirzaei
*/
public class BpmnParse extends Parse {
protected static final Logger LOGGER = Logger.getLogger(BpmnParse.class.getName());
public static final String PROPERTYNAME_DOCUMENTATION = "documentation";
public static final String PROPERTYNAME_INITIAL = "initial";
public static final String PROPERTYNAME_INITIATOR_VARIABLE_NAME = "initiatorVariableName";
public static final String PROPERTYNAME_CONDITION = "condition";
public static final String PROPERTYNAME_CONDITION_TEXT = "conditionText";
public static final String PROPERTYNAME_VARIABLE_DECLARATIONS = "variableDeclarations";
public static final String PROPERTYNAME_TIMER_DECLARATION = "timerDeclarations";
public static final String PROPERTYNAME_ISEXPANDED = "isExpanded";
public static final String PROPERTYNAME_START_TIMER = "timerStart";
public static final String PROPERTYNAME_SIGNAL_DEFINITION_NAME = "signalDefinition";
public static final String PROPERTYNAME_COMPENSATION_HANDLER_ID = "compensationHandler";
public static final String PROPERTYNAME_IS_FOR_COMPENSATION = "isForCompensation";
public static final String PROPERTYNAME_ERROR_EVENT_DEFINITIONS = "errorEventDefinitions";
public static final String PROPERTYNAME_MESSAGE_EVENT_DEFINITIONS = "messageEventDefinitions";
/* process start authorization specific finals */
protected static final String POTENTIAL_STARTER = "potentialStarter";
protected static final String CANDIDATE_STARTER_USERS_EXTENSION = "candidateStarterUsers";
protected static final String CANDIDATE_STARTER_GROUPS_EXTENSION = "candidateStarterGroups";
/** The deployment to which the parsed process definitions will be added. */
protected DeploymentEntity deployment;
/** The end result of the parsing: a list of process definition. */
protected List<ProcessDefinitionEntity> processDefinitions = new ArrayList<ProcessDefinitionEntity>();
/** Mapping of found errors in BPMN 2.0 file */
protected Map<String, Error> errors = new HashMap<String, Error>();
/** A map for storing sequence flow based on their id during parsing. */
protected Map<String, TransitionImpl> sequenceFlows;
/** A list of all element IDs. This allows us to parse only what we actually support but
* still validate the references among elements we do not support. */
protected List<String> elementIds = new ArrayList<String>();
/** A map for storing the process references of participants */
protected Map<String, String> participantProcesses = new HashMap<String, String>();
/**
* Mapping containing values stored during the first phase of parsing since
* other elements can reference these messages.
*
* All the map's elements are defined outside the process definition(s), which
* means that this map doesn't need to be re-initialized for each new process
* definition.
*/
protected Map<String, MessageDefinition> messages = new HashMap<String, MessageDefinition>();
protected Map<String, StructureDefinition> structures = new HashMap<String, StructureDefinition>();
protected Map<String, BpmnInterfaceImplementation> interfaceImplementations = new HashMap<String, BpmnInterfaceImplementation>();
protected Map<String, OperationImplementation> operationImplementations = new HashMap<String, OperationImplementation>();
protected Map<String, ItemDefinition> itemDefinitions = new HashMap<String, ItemDefinition>();
protected Map<String, BpmnInterface> bpmnInterfaces = new HashMap<String, BpmnInterface>();
protected Map<String, Operation> operations = new HashMap<String, Operation>();
protected Map<String, SignalDefinition> signals = new HashMap<String, SignalDefinition>();
// Members
protected ExpressionManager expressionManager;
protected List<BpmnParseListener> parseListeners;
protected Map<String, XMLImporter> importers = new HashMap<String, XMLImporter>();
protected Map<String, String> prefixs = new HashMap<String, String>();
protected String targetNamespace;
/**
* Constructor to be called by the {@link BpmnParser}.
*
* Note the package modifier here: only the {@link BpmnParser} is allowed to
* create instances.
*/
BpmnParse(BpmnParser parser) {
super(parser);
this.expressionManager = parser.getExpressionManager();
this.parseListeners = parser.getParseListeners();
setSchemaResource(ReflectUtil.getResource(BpmnParser.BPMN_20_SCHEMA_LOCATION).toString());
this.initializeXSDItemDefinitions();
}
protected void initializeXSDItemDefinitions() {
this.itemDefinitions.put("http://www.w3.org/2001/XMLSchema:string", new ItemDefinition("http://www.w3.org/2001/XMLSchema:string",
new ClassStructureDefinition(String.class)));
}
public BpmnParse deployment(DeploymentEntity deployment) {
this.deployment = deployment;
return this;
}
@Override
public BpmnParse execute() {
super.execute(); // schema validation
try {
parseRootElement();
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Unknown exception", e);
} finally {
if (hasWarnings()) {
logWarnings();
}
if (hasErrors()) {
throwActivitiExceptionForErrors();
}
}
return this;
}
/**
* Parses the 'definitions' root element
*/
protected void parseRootElement() {
collectElementIds();
parseDefinitionsAttributes();
parseImports();
parseItemDefinitions();
parseMessages();
parseInterfaces();
parseErrors();
parseSignals();
parseProcessDefinitions();
parseCollaboration();
// Diagram interchange parsing must be after parseProcessDefinitions,
// since it depends and sets values on existing process definition objects
parseDiagramInterchangeElements();
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseRootElement(rootElement, getProcessDefinitions());
}
}
protected void collectElementIds() {
rootElement.collectIds(elementIds);
}
protected void parseDefinitionsAttributes() {
String typeLanguage = rootElement.attribute("typeLanguage");
String expressionLanguage = rootElement.attribute("expressionLanguage");
this.targetNamespace = rootElement.attribute("targetNamespace");
if (typeLanguage != null) {
if (typeLanguage.contains("XMLSchema")) {
LOGGER.info("XMLSchema currently not supported as typeLanguage");
}
}
if (expressionLanguage != null) {
if (expressionLanguage.contains("XPath")) {
LOGGER.info("XPath currently not supported as expressionLanguage");
}
}
for (String attribute : rootElement.attributes()) {
if (attribute.startsWith("xmlns:")) {
String prefixValue = rootElement.attribute(attribute);
String prefixName = attribute.substring(6);
this.prefixs.put(prefixName, prefixValue);
}
}
}
protected String resolveName(String name) {
if (name == null) {
return null;
}
int indexOfP = name.indexOf(':');
if (indexOfP != -1) {
String prefix = name.substring(0, indexOfP);
String resolvedPrefix = this.prefixs.get(prefix);
return resolvedPrefix + ":" + name.substring(indexOfP + 1);
} else {
return name;
}
}
/**
* Parses the rootElement importing structures
*
* @param rootElement
* The root element of the XML file.
*/
protected void parseImports() {
List<Element> imports = rootElement.elements("import");
for (Element theImport : imports) {
String importType = theImport.attribute("importType");
XMLImporter importer = this.getImporter(importType, theImport);
if (importer == null) {
addError("Could not import item of type " + importType, theImport);
} else {
importer.importFrom(theImport, this);
}
}
}
protected XMLImporter getImporter(String importType, Element theImport) {
if (this.importers.containsKey(importType)) {
return this.importers.get(importType);
} else {
if (importType.equals("http://schemas.xmlsoap.org/wsdl/")) {
Class< ? > wsdlImporterClass;
try {
wsdlImporterClass = Class.forName("org.activiti.engine.impl.webservice.CxfWSDLImporter", true, Thread.currentThread().getContextClassLoader());
XMLImporter newInstance = (XMLImporter) wsdlImporterClass.newInstance();
this.importers.put(importType, newInstance);
return newInstance;
} catch (Exception e) {
addError("Could not find importer for type " + importType, theImport);
}
}
return null;
}
}
/**
* Parses the itemDefinitions of the given definitions file. Item definitions
* are not contained within a process element, but they can be referenced from
* inner process elements.
*
* @param definitionsElement
* The root element of the XML file.
*/
public void parseItemDefinitions() {
for (Element itemDefinitionElement : rootElement.elements("itemDefinition")) {
String id = itemDefinitionElement.attribute("id");
String structureRef = this.resolveName(itemDefinitionElement.attribute("structureRef"));
String itemKind = itemDefinitionElement.attribute("itemKind");
StructureDefinition structure = null;
try {
// it is a class
Class< ? > classStructure = ReflectUtil.loadClass(structureRef);
structure = new ClassStructureDefinition(classStructure);
} catch (ActivitiException e) {
// it is a reference to a different structure
structure = this.structures.get(structureRef);
}
ItemDefinition itemDefinition = new ItemDefinition(this.targetNamespace + ":" + id, structure);
if (itemKind != null) {
itemDefinition.setItemKind(ItemKind.valueOf(itemKind));
}
itemDefinitions.put(itemDefinition.getId(), itemDefinition);
}
}
/**
* Parses the messages of the given definitions file. Messages are not
* contained within a process element, but they can be referenced from inner
* process elements.
*
* @param definitionsElement
* The root element of the XML file/
*/
public void parseMessages() {
for (Element messageElement : rootElement.elements("message")) {
String id = messageElement.attribute("id");
String itemRef = this.resolveName(messageElement.attribute("itemRef"));
String name = messageElement.attribute("name");
MessageDefinition messageDefinition = new MessageDefinition(this.targetNamespace + ":" + id, name);
if(itemRef != null) {
if(!this.itemDefinitions.containsKey(itemRef)) {
addError(itemRef + " does not exist", messageElement);
} else {
ItemDefinition itemDefinition = this.itemDefinitions.get(itemRef);
messageDefinition.setItemDefinition(itemDefinition);
}
}
this.messages.put(messageDefinition.getId(), messageDefinition);
}
}
/**
* Parses the signals of the given definitions file. Signals are not
* contained within a process element, but they can be referenced from inner
* process elements.
*
* @param definitionsElement
* The root element of the XML file/
*/
protected void parseSignals() {
for (Element signalElement : rootElement.elements("signal")) {
String id = signalElement.attribute("id");
String signalName = this.resolveName(signalElement.attribute("name"));
for (SignalDefinition signalDefinition : signals.values()) {
if(signalDefinition.getName().equals(signalName)) {
addError("duplicate signal name '"+signalName+"'.", signalElement);
}
}
if(id == null) {
addError("signal must have an id", signalElement);
}
else if(signalName == null) {
addError("signal with id '"+id+"' has no name", signalElement);
}else {
SignalDefinition signal = new SignalDefinition();
signal.setId(this.targetNamespace + ":" + id);
signal.setName(signalName);
this.signals.put(id, signal);
}
}
}
/**
* Parses the interfaces and operations defined withing the root element.
*
* @param definitionsElement
* The root element of the XML file/
*/
public void parseInterfaces() {
for (Element interfaceElement : rootElement.elements("interface")) {
// Create the interface
String id = interfaceElement.attribute("id");
String name = interfaceElement.attribute("name");
String implementationRef = this.resolveName(interfaceElement.attribute("implementationRef"));
BpmnInterface bpmnInterface = new BpmnInterface(this.targetNamespace + ":" + id, name);
bpmnInterface.setImplementation(this.interfaceImplementations.get(implementationRef));
// Handle all its operations
for (Element operationElement : interfaceElement.elements("operation")) {
Operation operation = parseOperation(operationElement, bpmnInterface);
bpmnInterface.addOperation(operation);
}
bpmnInterfaces.put(bpmnInterface.getId(), bpmnInterface);
}
}
public Operation parseOperation(Element operationElement, BpmnInterface bpmnInterface) {
Element inMessageRefElement = operationElement.element("inMessageRef");
String inMessageRef = this.resolveName(inMessageRefElement.getText());
if (!this.messages.containsKey(inMessageRef)) {
addError(inMessageRef + " does not exist", inMessageRefElement);
return null;
} else {
MessageDefinition inMessage = this.messages.get(inMessageRef);
String id = operationElement.attribute("id");
String name = operationElement.attribute("name");
String implementationRef = this.resolveName(operationElement.attribute("implementationRef"));
Operation operation = new Operation(this.targetNamespace + ":" + id, name, bpmnInterface, inMessage);
operation.setImplementation(this.operationImplementations.get(implementationRef));
Element outMessageRefElement = operationElement.element("outMessageRef");
if (outMessageRefElement != null) {
String outMessageRef = this.resolveName(outMessageRefElement.getText());
if (this.messages.containsKey(outMessageRef)) {
MessageDefinition outMessage = this.messages.get(outMessageRef);
operation.setOutMessage(outMessage);
}
}
operations.put(operation.getId(), operation);
return operation;
}
}
public void parseErrors() {
for (Element errorElement : rootElement.elements("error")) {
Error error = new Error();
String id = errorElement.attribute("id");
if (id == null) {
addError("'id' is mandatory on error definition", errorElement);
}
error.setId(id);
String errorCode = errorElement.attribute("errorCode");
if (errorCode == null) {
addError("'errorCode' is mandatory on error definition", errorElement);
}
error.setErrorCode(errorCode);
errors.put(id, error);
}
}
/**
* Parses all the process definitions defined within the 'definitions' root
* element.
*
* @param definitionsElement
* The root element of the XML file.
*/
public void parseProcessDefinitions() {
for (Element processElement : rootElement.elements("process")) {
boolean processProcess = true;
String isExecutableStr = processElement.attribute("isExecutable");
if (isExecutableStr != null) {
boolean isExecutable = Boolean.parseBoolean(isExecutableStr);
if (!isExecutable) {
processProcess = false;
LOGGER.info("Ignoring non-executable process with id='" + processElement.attribute("id") + "'. Set the attribute executable=\"true\" to deploy this process.");
}
}
//Only process executable processes
if (processProcess) {
processDefinitions.add(parseProcess(processElement));
}
}
}
/**
* Parses the collaboration definition defined within the 'definitions'
* root element and get all participants to lookup their process references
* during DI parsing.
*/
public void parseCollaboration() {
Element collaboration = rootElement.element("collaboration");
if (collaboration != null) {
for (Element participant : collaboration.elements("participant")) {
String processRef = participant.attribute("processRef");
if (processRef != null) {
ProcessDefinitionImpl procDef = getProcessDefinition(processRef);
if(procDef != null) {
// Set participant process on the procDef, so it can get rendered later on if needed
ParticipantProcess participantProcess = new ParticipantProcess();
participantProcess.setId(participant.attribute("id"));
participantProcess.setName(participant.attribute("name"));
procDef.setParticipantProcess(participantProcess);
participantProcesses.put(participantProcess.getId(), processRef);
}
}
}
}
}
/**
* Parses one process (ie anything inside a <process> element).
*
* @param processElement
* The 'process' element.
* @return The parsed version of the XML: a {@link ProcessDefinitionImpl}
* object.
*/
public ProcessDefinitionEntity parseProcess(Element processElement) {
// reset all mappings that are related to one process definition
sequenceFlows = new HashMap<String, TransitionImpl>();
ProcessDefinitionEntity processDefinition = new ProcessDefinitionEntity();
/*
* Mapping object model - bpmn xml: processDefinition.id -> generated by
* activiti engine processDefinition.key -> bpmn id (required)
* processDefinition.name -> bpmn name (optional)
*/
processDefinition.setKey(processElement.attribute("id"));
processDefinition.setName(processElement.attribute("name"));
processDefinition.setCategory(rootElement.attribute("targetNamespace"));
processDefinition.setProperty(PROPERTYNAME_DOCUMENTATION, parseDocumentation(processElement));
processDefinition.setTaskDefinitions(new HashMap<String, TaskDefinition>());
processDefinition.setDeploymentId(deployment.getId());
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Parsing process " + processDefinition.getKey());
}
parseScope(processElement, processDefinition);
// Parse any laneSets defined for this process
parseLaneSets(processElement, processDefinition);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseProcess(processElement, processDefinition);
}
return processDefinition;
}
protected void parseLaneSets(Element parentElement, ProcessDefinitionEntity processDefinition) {
List<Element> laneSets = parentElement.elements("laneSet");
if(laneSets != null && laneSets.size() > 0) {
for(Element laneSetElement : laneSets) {
LaneSet newLaneSet = new LaneSet();
newLaneSet.setId(laneSetElement.attribute("id"));
newLaneSet.setName(laneSetElement.attribute("name"));
parseLanes(laneSetElement, newLaneSet);
// Finally, add the set
processDefinition.addLaneSet(newLaneSet);
}
}
}
protected void parseLanes(Element laneSetElement, LaneSet laneSet) {
List<Element> lanes = laneSetElement.elements("lane");
if(lanes != null && lanes.size() > 0) {
for(Element laneElement : lanes) {
// Parse basic attributes
Lane lane = new Lane();
lane.setId(laneElement.attribute("id"));
lane.setName(laneElement.attribute("name"));
// Parse ID's of flow-nodes that live inside this lane
List<Element> flowNodeElements = laneElement.elements("flowNodeRef");
if(flowNodeElements != null && flowNodeElements.size() > 0) {
for(Element flowNodeElement : flowNodeElements) {
lane.getFlowNodeIds().add(flowNodeElement.getText());
}
}
laneSet.addLane(lane);
}
}
}
/**
* Parses a scope: a process, subprocess, etc.
*
* Note that a process definition is a scope on itself.
*
* @param scopeElement
* The XML element defining the scope
* @param parentScope
* The scope that contains the nested scope.
*/
public void parseScope(Element scopeElement, ScopeImpl parentScope) {
// Not yet supported on process level (PVM additions needed):
// parseProperties(processElement);
HashMap<String, Element> postponedElements = new HashMap<String, Element>();
parseStartEvents(scopeElement, parentScope);
parseActivities(scopeElement, parentScope, postponedElements);
parsePostponedElements(scopeElement, parentScope, postponedElements);
parseEndEvents(scopeElement, parentScope);
parseBoundaryEvents(scopeElement, parentScope);
parseSequenceFlow(scopeElement, parentScope);
parseExecutionListenersOnScope(scopeElement, parentScope);
parseAssociations(scopeElement, parentScope);
if(parentScope instanceof ProcessDefinition) {
parseProcessDefinitionCustomExtensions(scopeElement, (ProcessDefinition) parentScope);
}
postponedElements.clear();
IOSpecification ioSpecification = parseIOSpecification(scopeElement.element("ioSpecification"));
parentScope.setIoSpecification(ioSpecification);
}
protected void parsePostponedElements(Element scopeElement, ScopeImpl parentScope, HashMap<String, Element> postponedElements) {
for (Element postponedElement : postponedElements.values()) {
if(parentScope.findActivity(postponedElement.attribute("id")) == null) { // check whether activity is already parsed
if(postponedElement.getTagName().equals("intermediateCatchEvent")) {
parseIntermediateCatchEvent(postponedElement, parentScope, false);
}
}
}
}
protected void parseProcessDefinitionCustomExtensions(Element scopeElement, ProcessDefinition definition) {
parseStartAuthorization(scopeElement, definition);
}
protected void parseStartAuthorization(Element scopeElement, ProcessDefinition definition) {
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) definition;
// parse activiti:potentialStarters
Element extentionsElement = scopeElement.element("extensionElements");
if (extentionsElement != null) {
List<Element> potentialStarterElements = extentionsElement.elementsNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, POTENTIAL_STARTER);
for (Element potentialStarterElement : potentialStarterElements) {
parsePotentialStarterResourceAssignment(potentialStarterElement, processDefinition);
}
}
// parse activiti:candidateStarterUsers
String candidateUsersString = scopeElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, CANDIDATE_STARTER_USERS_EXTENSION);
if (candidateUsersString != null) {
List<String> candidateUsers = parseCommaSeparatedList(candidateUsersString);
for (String candidateUser : candidateUsers) {
processDefinition.addCandidateStarterUserIdExpression(expressionManager.createExpression(candidateUser.trim()));
}
}
// Candidate activiti:candidateStarterGroups
String candidateGroupsString = scopeElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, CANDIDATE_STARTER_GROUPS_EXTENSION);
if (candidateGroupsString != null) {
List<String> candidateGroups = parseCommaSeparatedList(candidateGroupsString);
for (String candidateGroup : candidateGroups) {
processDefinition.addCandidateStarterGroupIdExpression(expressionManager.createExpression(candidateGroup.trim()));
}
}
}
protected void parsePotentialStarterResourceAssignment(Element performerElement, ProcessDefinitionEntity processDefinition) {
Element raeElement = performerElement.element(RESOURCE_ASSIGNMENT_EXPR);
if (raeElement != null) {
Element feElement = raeElement.element(FORMAL_EXPRESSION);
if (feElement != null) {
List<String> assignmentExpressions = parseCommaSeparatedList(feElement.getText());
for (String assignmentExpression : assignmentExpressions) {
assignmentExpression = assignmentExpression.trim();
if (assignmentExpression.startsWith(USER_PREFIX)) {
String userAssignementId = getAssignmentId(assignmentExpression, USER_PREFIX);
processDefinition.addCandidateStarterUserIdExpression(expressionManager.createExpression(userAssignementId));
} else if (assignmentExpression.startsWith(GROUP_PREFIX)) {
String groupAssignementId = getAssignmentId(assignmentExpression, GROUP_PREFIX);
processDefinition.addCandidateStarterGroupIdExpression(expressionManager.createExpression(groupAssignementId));
} else { // default: given string is a goupId, as-is.
processDefinition.addCandidateStarterGroupIdExpression(expressionManager.createExpression(assignmentExpression));
}
}
}
}
}
protected void parseAssociations(Element scopeElement, ScopeImpl parentScope) {
for (Element associationElement : scopeElement.elements("association")) {
String sourceRef = associationElement.attribute("sourceRef");
if(sourceRef == null) {
addError("association element missing attribute 'sourceRef'", associationElement);
}
String targetRef = associationElement.attribute("targetRef");
if(targetRef == null) {
addError("association element missing attribute 'targetRef'", associationElement);
}
ActivityImpl sourceActivity = parentScope.findActivity(sourceRef);
ActivityImpl targetActivity = parentScope.findActivity(targetRef);
// an association may reference elements that are not parsed as activities (like for instance
// text annotations so do not throw an exception if sourceActivity or targetActivity are null)
// However, we make sure they reference 'something':
if(sourceActivity == null && !elementIds.contains(sourceRef)) {
addError("Invalid reference sourceRef '"+sourceRef+"' of association element ", associationElement);
} else if(targetActivity == null && !elementIds.contains(targetRef)) {
addError("Invalid reference targetRef '"+targetRef+"' of association element ", associationElement);
} else {
if(sourceActivity != null && sourceActivity.getProperty("type").equals("compensationBoundaryCatch")) {
Object isForCompensation = targetActivity.getProperty(PROPERTYNAME_IS_FOR_COMPENSATION);
if(isForCompensation == null || !(Boolean) isForCompensation) {
addError("compensation boundary catch must be connected to element with isForCompensation=true", associationElement);
} else {
ActivityImpl compensatedActivity = sourceActivity.getParentActivity();
compensatedActivity.setProperty(PROPERTYNAME_COMPENSATION_HANDLER_ID, targetActivity.getId());
}
}
}
}
}
protected IOSpecification parseIOSpecification(Element ioSpecificationElement) {
if (ioSpecificationElement == null) {
return null;
}
IOSpecification ioSpecification = new IOSpecification();
for (Element dataInputElement : ioSpecificationElement.elements("dataInput")) {
String id = dataInputElement.attribute("id");
String itemSubjectRef = this.resolveName(dataInputElement.attribute("itemSubjectRef"));
ItemDefinition itemDefinition = this.itemDefinitions.get(itemSubjectRef);
Data dataInput = new Data(this.targetNamespace + ":" + id, id, itemDefinition);
ioSpecification.addInput(dataInput);
}
for (Element dataOutputElement : ioSpecificationElement.elements("dataOutput")) {
String id = dataOutputElement.attribute("id");
String itemSubjectRef = this.resolveName(dataOutputElement.attribute("itemSubjectRef"));
ItemDefinition itemDefinition = this.itemDefinitions.get(itemSubjectRef);
Data dataOutput = new Data(this.targetNamespace + ":" + id, id, itemDefinition);
ioSpecification.addOutput(dataOutput);
}
for (Element inputSetElement : ioSpecificationElement.elements("inputSet")) {
for (Element dataInputRef : inputSetElement.elements("dataInputRefs")) {
DataRef dataRef = new DataRef(dataInputRef.getText());
ioSpecification.addInputRef(dataRef);
}
}
for (Element outputSetElement : ioSpecificationElement.elements("outputSet")) {
for (Element dataInputRef : outputSetElement.elements("dataOutputRefs")) {
DataRef dataRef = new DataRef(dataInputRef.getText());
ioSpecification.addOutputRef(dataRef);
}
}
return ioSpecification;
}
protected AbstractDataAssociation parseDataInputAssociation(Element dataAssociationElement) {
String sourceRef = dataAssociationElement.element("sourceRef").getText();
String targetRef = dataAssociationElement.element("targetRef").getText();
List<Element> assignments = dataAssociationElement.elements("assignment");
if (assignments.isEmpty()) {
return new MessageImplicitDataInputAssociation(sourceRef, targetRef);
} else {
SimpleDataInputAssociation dataAssociation = new SimpleDataInputAssociation(sourceRef, targetRef);
for (Element assigmentElement : dataAssociationElement.elements("assignment")) {
Expression from = this.expressionManager.createExpression(assigmentElement.element("from").getText());
Expression to = this.expressionManager.createExpression(assigmentElement.element("to").getText());
Assignment assignment = new Assignment(from, to);
dataAssociation.addAssignment(assignment);
}
return dataAssociation;
}
}
/**
* Parses the start events of a certain level in the process (process,
* subprocess or another scope).
*
* @param parentElement
* The 'parent' element that contains the start events (process,
* subprocess).
* @param scope
* The {@link ScopeImpl} to which the start events must be added.
*/
public void parseStartEvents(Element parentElement, ScopeImpl scope) {
List<Element> startEventElements = parentElement.elements("startEvent");
List<ActivityImpl> startEventActivities = new ArrayList<ActivityImpl>();
for (Element startEventElement : startEventElements) {
ActivityImpl startEventActivity = createActivityOnScope(startEventElement, scope);
if (scope instanceof ProcessDefinitionEntity) {
parseProcessDefinitionStartEvent(startEventActivity, startEventElement, parentElement, scope);
startEventActivities.add(startEventActivity);
} else {
parseScopeStartEvent(startEventActivity, startEventElement, parentElement, scope);
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseStartEvent(startEventElement, scope, startEventActivity);
}
parseExecutionListenersOnScope(startEventElement, startEventActivity);
}
if(scope instanceof ProcessDefinitionEntity) {
selectInitial(startEventActivities, (ProcessDefinitionEntity) scope, parentElement);
parseStartFormHandlers(startEventElements, (ProcessDefinitionEntity) scope);
}
}
protected void selectInitial(List<ActivityImpl> startEventActivities, ProcessDefinitionEntity processDefinition, Element parentElement) {
ActivityImpl initial = null;
// validate that there is s single none start event / timer start event:
for (ActivityImpl activityImpl : startEventActivities) {
if(!activityImpl.getProperty("type").equals("messageStartEvent")) {
if(initial == null) {
initial = activityImpl;
} else {
addError("multiple none start events or timer start events not supported on process definition", parentElement);
}
}
}
// if there is a single start event, select it as initial, regardless of it's type:
if(initial == null && startEventActivities.size() == 1) {
initial = startEventActivities.get(0);
}
processDefinition.setInitial(initial);
}
protected void parseProcessDefinitionStartEvent(ActivityImpl startEventActivity, Element startEventElement, Element parentElement, ScopeImpl scope) {
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) scope;
String initiatorVariableName = startEventElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "initiator");
if (initiatorVariableName != null) {
processDefinition.setProperty(PROPERTYNAME_INITIATOR_VARIABLE_NAME, initiatorVariableName);
}
// all start events share the same behavior:
startEventActivity.setActivityBehavior(new NoneStartEventActivityBehavior());
Element timerEventDefinition = startEventElement.element("timerEventDefinition");
Element messageEventDefinition = startEventElement.element("messageEventDefinition");
if (timerEventDefinition != null) {
parseTimerStartEventDefinition(timerEventDefinition, startEventActivity, processDefinition);
} else if(messageEventDefinition != null) {
parseMessageStartEventDefinition(messageEventDefinition, startEventActivity, processDefinition);
}
}
protected void parseStartFormHandlers(List<Element> startEventElements, ProcessDefinitionEntity processDefinition) {
if(processDefinition.getInitial() != null) {
for (Element startEventElement : startEventElements) {
if(startEventElement.attribute("id").equals(processDefinition.getInitial().getId())) {
StartFormHandler startFormHandler;
String startFormHandlerClassName = startEventElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "formHandlerClass");
if (startFormHandlerClassName != null) {
startFormHandler = (StartFormHandler) ReflectUtil.instantiate(startFormHandlerClassName);
} else {
startFormHandler = new DefaultStartFormHandler();
}
startFormHandler.parseConfiguration(startEventElement, deployment, processDefinition, this);
processDefinition.setStartFormHandler(startFormHandler);
}
}
}
}
protected void parseScopeStartEvent(ActivityImpl startEventActivity, Element startEventElement, Element parentElement, ScopeImpl scope) {
if(scope.getProperty(PROPERTYNAME_INITIAL) == null) {
scope.setProperty(PROPERTYNAME_INITIAL, startEventActivity);
Object triggeredByEvent = scope.getProperty("triggeredByEvent");
boolean isTriggeredByEvent = triggeredByEvent!=null && ((Boolean)triggeredByEvent==true);
Element errorEventDefinition = startEventElement.element("errorEventDefinition");
if (errorEventDefinition != null) {
if(isTriggeredByEvent) {
parseErrorStartEventDefinition(errorEventDefinition, startEventActivity, scope);
} else {
addError("errorEventDefinition only allowed on start event if subprocess is an event subprocess", errorEventDefinition);
}
} else {
if(!isTriggeredByEvent) {
startEventActivity.setActivityBehavior(new NoneStartEventActivityBehavior());
} else {
addError("none start event not allowed for event subprocess", startEventElement);
}
}
} else {
addError("multiple start events not supported for subprocess", startEventElement);
}
}
protected void parseErrorStartEventDefinition(Element errorEventDefinition, ActivityImpl startEventActivity, ScopeImpl scope) {
startEventActivity.setProperty("type", "errorStartEvent");
String errorRef = errorEventDefinition.attribute("errorRef");
Error error = null;
ErrorEventDefinition definition = new ErrorEventDefinition(startEventActivity.getId());
if (errorRef != null) {
error = errors.get(errorRef);
String errorCode = error == null ? errorRef : error.getErrorCode();
definition.setErrorCode(errorCode);
}
ScopeImpl catchingScope = ((ActivityImpl)scope).getParent();
definition.setPrecedence(10);
addErrorEventDefinition(definition, catchingScope);
startEventActivity.setActivityBehavior(new EventSubProcessStartEventActivityBehavior());
}
protected void parseMessageStartEventDefinition(Element messageEventDefinition, ActivityImpl startEventActivity, ProcessDefinitionEntity processDefinition) {
String messageRef = messageEventDefinition.attribute("messageRef");
if(messageRef == null) {
addError("attriute 'messageRef' is required", messageEventDefinition);
}
MessageDefinition messageDefinition = messages.get(resolveName(messageRef));
if(messageDefinition == null) {
addError("Invalid 'messageRef': no message with id '"+messageRef+"' found.", messageEventDefinition);
}
startEventActivity.setProperty("type", "messageStartEvent");
// create message event subscription:
MessageEventDefinition subscription = new MessageEventDefinition(messageDefinition.getId(), messageDefinition.getName(), startEventActivity.getId());
subscription.setStartEvent(true);
addMessageEventDefinition(subscription, processDefinition);
}
@SuppressWarnings("unchecked")
protected void addMessageEventDefinition(MessageEventDefinition subscription, ScopeImpl scope) {
List<MessageEventDefinition> messageEventDefinitions = (List<MessageEventDefinition>) scope.getProperty(PROPERTYNAME_MESSAGE_EVENT_DEFINITIONS);
if(messageEventDefinitions == null) {
messageEventDefinitions = new ArrayList<MessageEventDefinition>();
scope.setProperty(PROPERTYNAME_MESSAGE_EVENT_DEFINITIONS, messageEventDefinitions);
}
messageEventDefinitions.add(subscription);
}
/**
* Parses the activities of a certain level in the process (process,
* subprocess or another scope).
*
* @param parentElement
* The 'parent' element that contains the activities (process,
* subprocess).
* @param scopeElement
* The {@link ScopeImpl} to which the activities must be added.
* @param postponedElements
* @param postProcessActivities
*/
public void parseActivities(Element parentElement, ScopeImpl scopeElement, HashMap<String, Element> postponedElements) {
for (Element activityElement : parentElement.elements()) {
parseActivity(activityElement, parentElement, scopeElement, postponedElements);
}
}
protected void parseActivity(Element activityElement, Element parentElement, ScopeImpl scopeElement, HashMap<String, Element> postponedElements) {
ActivityImpl activity = null;
if (activityElement.getTagName().equals("exclusiveGateway")) {
activity = parseExclusiveGateway(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("inclusiveGateway")) {
activity = parseInclusiveGateway(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("parallelGateway")) {
activity = parseParallelGateway(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("scriptTask")) {
activity = parseScriptTask(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("serviceTask")) {
activity = parseServiceTask(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("businessRuleTask")) {
activity = parseBusinessRuleTask(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("task")) {
activity = parseTask(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("manualTask")) {
activity = parseManualTask(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("userTask")) {
activity = parseUserTask(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("sendTask")) {
activity = parseSendTask(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("receiveTask")) {
activity = parseReceiveTask(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("subProcess")) {
activity = parseSubProcess(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("callActivity")) {
activity = parseCallActivity(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("intermediateCatchEvent")) {
// postpone all intermediate catch events (required for supporting event-based gw)
postponedElements.put(activityElement.attribute("id"), activityElement);
} else if (activityElement.getTagName().equals("intermediateThrowEvent")) {
activity = parseIntermediateThrowEvent(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("eventBasedGateway")) {
activity = parseEventBasedGateway(activityElement, parentElement, scopeElement);
} else if(activityElement.getTagName().equals("transaction")) {
activity = parseTransaction(activityElement, scopeElement);
} else if (activityElement.getTagName().equals("adHocSubProcess") || activityElement.getTagName().equals("complexGateway")) {
addWarning("Ignoring unsupported activity type", activityElement);
}
// Parse stuff common to activities above
if (activity != null) {
parseMultiInstanceLoopCharacteristics(activityElement, activity);
}
}
public ActivityImpl parseIntermediateCatchEvent(Element intermediateEventElement, ScopeImpl scopeElement, boolean isAfterEventBasedGateway) {
ActivityImpl nestedActivity = createActivityOnScope(intermediateEventElement, scopeElement);
// Catch event behavior is the same for all types
nestedActivity.setActivityBehavior(new IntermediateCatchEventActivitiBehaviour());
Element timerEventDefinition = intermediateEventElement.element("timerEventDefinition");
Element signalEventDefinition = intermediateEventElement.element("signalEventDefinition");
if (timerEventDefinition != null) {
parseIntemediateTimerEventDefinition(timerEventDefinition, nestedActivity, isAfterEventBasedGateway);
}else if(signalEventDefinition != null) {
parseIntemediateSignalEventDefinition(signalEventDefinition, nestedActivity, isAfterEventBasedGateway);
} else {
addError("Unsupported intermediate catch event type", intermediateEventElement);
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseIntermediateCatchEvent(intermediateEventElement, scopeElement, nestedActivity);
}
parseExecutionListenersOnScope(intermediateEventElement, nestedActivity);
return nestedActivity;
}
public ActivityImpl parseIntermediateThrowEvent(Element intermediateEventElement, ScopeImpl scopeElement) {
ActivityImpl nestedActivityImpl = createActivityOnScope(intermediateEventElement, scopeElement);
ActivityBehavior activityBehavior = null;
Element signalEventDefinitionElement = intermediateEventElement.element("signalEventDefinition");
Element compensateEventDefinitionElement = intermediateEventElement.element("compensateEventDefinition");
boolean otherUnsupportedThrowingIntermediateEvent =
(intermediateEventElement.element("escalationEventDefinition") != null) || //
(intermediateEventElement.element("messageEventDefinition") != null) || //
(intermediateEventElement.element("linkEventDefinition") != null);
// All other event definition types cannot be intermediate throwing (cancelEventDefinition, conditionalEventDefinition, errorEventDefinition, terminateEventDefinition, timerEventDefinition
if(signalEventDefinitionElement != null) {
nestedActivityImpl.setProperty("type", "intermediateSignalThrow");
SignalEventDefinition signalDefinition = parseSignalEventDefinition(signalEventDefinitionElement);
activityBehavior = new IntermediateThrowSignalEventActivityBehavior(signalDefinition);
} else if(compensateEventDefinitionElement != null) {
CompensateEventDefinition compensateEventDefinition = parseCompensateEventDefinition(compensateEventDefinitionElement, scopeElement);
activityBehavior = new IntermediateThrowCompensationEventActivityBehavior(compensateEventDefinition);
// IntermediateThrowNoneEventActivityBehavior
} else if (otherUnsupportedThrowingIntermediateEvent) {
addError("Unsupported intermediate throw event type", intermediateEventElement);
} else { // None intermediate event
activityBehavior = new IntermediateThrowNoneEventActivityBehavior();
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseIntermediateThrowEvent(intermediateEventElement, scopeElement, nestedActivityImpl);
}
nestedActivityImpl.setActivityBehavior(activityBehavior);
parseExecutionListenersOnScope(intermediateEventElement, nestedActivityImpl);
return nestedActivityImpl;
}
protected CompensateEventDefinition parseCompensateEventDefinition(Element compensateEventDefinitionElement, ScopeImpl scopeElement) {
String activityRef = compensateEventDefinitionElement.attribute("activityRef");
boolean waitForCompletion = "true".equals(compensateEventDefinitionElement.attribute("waitForCompletion", "true"));
if(activityRef != null) {
if(scopeElement.findActivity(activityRef) == null) {
addError("Invalid attribute value for 'activityRef': no activity with id '"+activityRef+"' in current scope", compensateEventDefinitionElement);
}
}
CompensateEventDefinition compensateEventDefinition = new CompensateEventDefinition();
compensateEventDefinition.setActivityRef(activityRef);
compensateEventDefinition.setWaitForCompletion(waitForCompletion);
return compensateEventDefinition;
}
protected void parseCatchCompensateEventDefinition(Element compensateEventDefinition, ActivityImpl activity) {
activity.setProperty("type", "compensationBoundaryCatch");
ScopeImpl parent = activity.getParent();
for (ActivityImpl child : parent.getActivities()) {
if(child.getProperty("type").equals("compensationBoundaryCatch")
&& child != activity ) {
addError("multiple boundary events with compensateEventDefinition not supported on same activity", compensateEventDefinition);
}
}
}
protected ActivityBehavior parseBoundaryCancelEventDefinition(Element cancelEventDefinition, ActivityImpl activity) {
activity.setProperty("type", "cancelBoundaryCatch");
ActivityImpl parent = (ActivityImpl) activity.getParent();
if(!parent.getProperty("type").equals("transaction")) {
addError("boundary event with cancelEventDefinition only supported on transaction subprocesses", cancelEventDefinition);
}
for (ActivityImpl child : parent.getActivities()) {
if(child.getProperty("type").equals("cancelBoundaryCatch")
&& child != activity ) {
addError("multiple boundary events with cancelEventDefinition not supported on same transaction subprocess", cancelEventDefinition);
}
}
return new CancelBoundaryEventActivityBehavior();
}
/**
* Parses loopCharacteristics (standardLoop/Multi-instance) of an activity, if
* any is defined.
*/
public void parseMultiInstanceLoopCharacteristics(Element activityElement, ActivityImpl activity) {
// Only 'activities' (in the BPMN 2.0 spec meaning) can have mi
// characteristics
if (!(activity.getActivityBehavior() instanceof AbstractBpmnActivityBehavior)) {
return;
}
Element miLoopCharacteristics = activityElement.element("multiInstanceLoopCharacteristics");
if (miLoopCharacteristics != null) {
MultiInstanceActivityBehavior miActivityBehavior = null;
boolean isSequential = parseBooleanAttribute(miLoopCharacteristics.attribute("isSequential"), false);
if (isSequential) {
miActivityBehavior = new SequentialMultiInstanceBehavior(activity, (AbstractBpmnActivityBehavior) activity.getActivityBehavior());
} else {
miActivityBehavior = new ParallelMultiInstanceBehavior(activity, (AbstractBpmnActivityBehavior) activity.getActivityBehavior());
}
activity.setScope(true);
activity.setProperty("multiInstance", isSequential ? "sequential" : "parallel");
activity.setActivityBehavior(miActivityBehavior);
// loopCardinality
Element loopCardinality = miLoopCharacteristics.element("loopCardinality");
if (loopCardinality != null) {
String loopCardinalityText = loopCardinality.getText();
if (loopCardinalityText == null || "".equals(loopCardinalityText)) {
addError("loopCardinality must be defined for a multiInstanceLoopCharacteristics definition ", miLoopCharacteristics);
}
miActivityBehavior.setLoopCardinalityExpression(expressionManager.createExpression(loopCardinalityText));
}
// completionCondition
Element completionCondition = miLoopCharacteristics.element("completionCondition");
if (completionCondition != null) {
String completionConditionText = completionCondition.getText();
miActivityBehavior.setCompletionConditionExpression(expressionManager.createExpression(completionConditionText));
}
// activiti:collection
String collection = miLoopCharacteristics.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "collection");
if (collection != null) {
if (collection.contains("{")) {
miActivityBehavior.setCollectionExpression(expressionManager.createExpression(collection));
} else {
miActivityBehavior.setCollectionVariable(collection);
}
}
// loopDataInputRef
Element loopDataInputRef = miLoopCharacteristics.element("loopDataInputRef");
if (loopDataInputRef != null) {
String loopDataInputRefText = loopDataInputRef.getText();
if (loopDataInputRefText != null) {
if (loopDataInputRefText.contains("{")) {
miActivityBehavior.setCollectionExpression(expressionManager.createExpression(loopDataInputRefText));
} else {
miActivityBehavior.setCollectionVariable(loopDataInputRefText);
}
}
}
// activiti:elementVariable
String elementVariable = miLoopCharacteristics.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "elementVariable");
if (elementVariable != null) {
miActivityBehavior.setCollectionElementVariable(elementVariable);
}
// dataInputItem
Element inputDataItem = miLoopCharacteristics.element("inputDataItem");
if (inputDataItem != null) {
String inputDataItemName = inputDataItem.attribute("name");
miActivityBehavior.setCollectionElementVariable(inputDataItemName);
}
// Validation
if (miActivityBehavior.getLoopCardinalityExpression() == null && miActivityBehavior.getCollectionExpression() == null
&& miActivityBehavior.getCollectionVariable() == null) {
addError("Either loopCardinality or loopDataInputRef/activiti:collection must been set", miLoopCharacteristics);
}
// Validation
if (miActivityBehavior.getCollectionExpression() == null && miActivityBehavior.getCollectionVariable() == null
&& miActivityBehavior.getCollectionElementVariable() != null) {
addError("LoopDataInputRef/activiti:collection must be set when using inputDataItem or activiti:elementVariable", miLoopCharacteristics);
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseMultiInstanceLoopCharacteristics(activityElement, miLoopCharacteristics, activity);
}
}
}
/**
* Parses the generic information of an activity element (id, name,
* documentation, etc.), and creates a new {@link ActivityImpl} on the given
* scope element.
*/
public ActivityImpl createActivityOnScope(Element activityElement, ScopeImpl scopeElement) {
String id = activityElement.attribute("id");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Parsing activity " + id);
}
ActivityImpl activity = scopeElement.createActivity(id);
activity.setProperty("name", activityElement.attribute("name"));
activity.setProperty("documentation", parseDocumentation(activityElement));
activity.setProperty("default", activityElement.attribute("default"));
activity.setProperty("type", activityElement.getTagName());
activity.setProperty("line", activityElement.getLine());
String isForCompensation = activityElement.attribute("isForCompensation");
if(isForCompensation != null && (isForCompensation.equals("true")||isForCompensation.equals("TRUE"))) {
activity.setProperty(PROPERTYNAME_IS_FOR_COMPENSATION, true);
}
return activity;
}
public String parseDocumentation(Element element) {
Element docElement = element.element("documentation");
if (docElement != null) {
return docElement.getText().trim();
}
return null;
}
/**
* Parses an exclusive gateway declaration.
*/
public ActivityImpl parseExclusiveGateway(Element exclusiveGwElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(exclusiveGwElement, scope);
activity.setActivityBehavior(new ExclusiveGatewayActivityBehavior());
parseExecutionListenersOnScope(exclusiveGwElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseExclusiveGateway(exclusiveGwElement, scope, activity);
}
return activity;
}
/**
* Parses an inclusive gateway declaration.
*/
public ActivityImpl parseInclusiveGateway(Element inclusiveGwElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(inclusiveGwElement, scope);
activity.setActivityBehavior(new InclusiveGatewayActivityBehavior());
parseExecutionListenersOnScope(inclusiveGwElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseInclusiveGateway(inclusiveGwElement, scope, activity);
}
return activity;
}
public ActivityImpl parseEventBasedGateway(Element eventBasedGwElement, Element parentElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(eventBasedGwElement, scope);
activity.setActivityBehavior(new EventBasedGatewayActivityBehavior());
activity.setScope(true);
parseExecutionListenersOnScope(eventBasedGwElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseEventBasedGateway(eventBasedGwElement, scope, activity);
}
// find all outgoing sequence flows:
List<Element> sequenceFlows = parentElement.elements("sequenceFlow");
// collect all siblings in a map
Map<String, Element> siblingsMap = new HashMap<String, Element>();
List<Element> siblings = parentElement.elements();
for (Element sibling : siblings) {
siblingsMap.put(sibling.attribute("id"), sibling);
}
for (Element sequenceFlow : sequenceFlows) {
String sourceRef = sequenceFlow.attribute("sourceRef");
String targetRef = sequenceFlow.attribute("targetRef");
if (activity.getId().equals(sourceRef)) {
Element sibling = siblingsMap.get(targetRef);
if (sibling != null) {
if (sibling.getTagName().equals("intermediateCatchEvent")) {
parseIntermediateCatchEvent(sibling, activity, true);
} else {
addError("Event based gateway can only be connected to elements of type intermediateCatchEvent", sibling);
}
}
}
}
return activity;
}
/**
* Parses a parallel gateway declaration.
*/
public ActivityImpl parseParallelGateway(Element parallelGwElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(parallelGwElement, scope);
activity.setActivityBehavior(new ParallelGatewayActivityBehavior());
parseExecutionListenersOnScope(parallelGwElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseParallelGateway(parallelGwElement, scope, activity);
}
return activity;
}
/**
* Parses a scriptTask declaration.
*/
public ActivityImpl parseScriptTask(Element scriptTaskElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(scriptTaskElement, scope);
String script = null;
String language = null;
String resultVariableName = null;
Element scriptElement = scriptTaskElement.element("script");
if (scriptElement != null) {
script = scriptElement.getText();
if (language == null) {
language = scriptTaskElement.attribute("scriptFormat");
}
if (language == null) {
language = ScriptingEngines.DEFAULT_SCRIPTING_LANGUAGE;
}
resultVariableName = scriptTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "resultVariable");
if (resultVariableName == null) {
// for backwards compatible reasons
resultVariableName = scriptTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "resultVariableName");
}
}
activity.setAsync(isAsync(scriptTaskElement));
activity.setExclusive(isExclusive(scriptTaskElement));
activity.setActivityBehavior(new ScriptTaskActivityBehavior(script, language, resultVariableName));
parseExecutionListenersOnScope(scriptTaskElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseScriptTask(scriptTaskElement, scope, activity);
}
return activity;
}
/**
* Parses a serviceTask declaration.
*/
public ActivityImpl parseServiceTask(Element serviceTaskElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(serviceTaskElement, scope);
String type = serviceTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "type");
String className = serviceTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "class");
String expression = serviceTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "expression");
String delegateExpression = serviceTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "delegateExpression");
String resultVariableName = serviceTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "resultVariable");
if (resultVariableName == null) {
resultVariableName = serviceTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "resultVariableName");
}
String implementation = serviceTaskElement.attribute("implementation");
String operationRef = this.resolveName(serviceTaskElement.attribute("operationRef"));
activity.setAsync(isAsync(serviceTaskElement));
activity.setExclusive(isExclusive(serviceTaskElement));
if (type != null) {
if (type.equalsIgnoreCase("mail")) {
parseEmailServiceTask(activity, serviceTaskElement, parseFieldDeclarations(serviceTaskElement));
} else if (type.equalsIgnoreCase("mule")) {
parseMuleServiceTask(activity, serviceTaskElement, parseFieldDeclarations(serviceTaskElement));
} else if (type.equalsIgnoreCase("shell")) {
parseShellServiceTask(activity, serviceTaskElement, parseFieldDeclarations(serviceTaskElement));
} else {
addError("Invalid usage of type attribute: '" + type + "'", serviceTaskElement);
}
} else if (className != null && className.trim().length() > 0) {
if (resultVariableName != null) {
addError("'resultVariableName' not supported for service tasks using 'class'", serviceTaskElement);
}
activity.setActivityBehavior(new ClassDelegate(className, parseFieldDeclarations(serviceTaskElement)));
} else if (delegateExpression != null) {
if (resultVariableName != null) {
addError("'resultVariableName' not supported for service tasks using 'delegateExpression'", serviceTaskElement);
}
activity.setActivityBehavior(new ServiceTaskDelegateExpressionActivityBehavior(expressionManager.createExpression(delegateExpression)));
} else if (expression != null && expression.trim().length() > 0) {
activity.setActivityBehavior(new ServiceTaskExpressionActivityBehavior(expressionManager.createExpression(expression), resultVariableName));
} else if (implementation != null && operationRef != null && implementation.equalsIgnoreCase("##WebService")) {
if (!this.operations.containsKey(operationRef)) {
addError(operationRef + " does not exist", serviceTaskElement);
} else {
Operation operation = this.operations.get(operationRef);
WebServiceActivityBehavior webServiceActivityBehavior = new WebServiceActivityBehavior(operation);
Element ioSpecificationElement = serviceTaskElement.element("ioSpecification");
if (ioSpecificationElement != null) {
IOSpecification ioSpecification = this.parseIOSpecification(ioSpecificationElement);
webServiceActivityBehavior.setIoSpecification(ioSpecification);
}
for (Element dataAssociationElement : serviceTaskElement.elements("dataInputAssociation")) {
AbstractDataAssociation dataAssociation = this.parseDataInputAssociation(dataAssociationElement);
webServiceActivityBehavior.addDataInputAssociation(dataAssociation);
}
for (Element dataAssociationElement : serviceTaskElement.elements("dataOutputAssociation")) {
AbstractDataAssociation dataAssociation = this.parseDataOutputAssociation(dataAssociationElement);
webServiceActivityBehavior.addDataOutputAssociation(dataAssociation);
}
activity.setActivityBehavior(webServiceActivityBehavior);
}
} else {
addError("One of the attributes 'class', 'delegateExpression', 'type', 'operation', or 'expression' is mandatory on serviceTask.", serviceTaskElement);
}
parseExecutionListenersOnScope(serviceTaskElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseServiceTask(serviceTaskElement, scope, activity);
}
return activity;
}
/**
* Parses a businessRuleTask declaration.
*/
public ActivityImpl parseBusinessRuleTask(Element businessRuleTaskElement, ScopeImpl scope) {
if (businessRuleTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "class")!=null
|| businessRuleTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "expression") !=null
|| businessRuleTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "delegateExpression") != null) {
// ACT-1164: If expression or class is set on a BusinessRuleTask it behaves like a service task
// to allow implementing the rule handling yourself
return parseServiceTask(businessRuleTaskElement, scope);
}
else {
ActivityImpl activity = createActivityOnScope(businessRuleTaskElement, scope);
BusinessRuleTaskActivityBehavior ruleActivity = new BusinessRuleTaskActivityBehavior();
String ruleVariableInputString = businessRuleTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "ruleVariablesInput");
String rulesString = businessRuleTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "rules");
String excludeString = businessRuleTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "exclude");
String resultVariableNameString = businessRuleTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "resultVariable");
activity.setAsync(isAsync(businessRuleTaskElement));
activity.setExclusive(isExclusive(businessRuleTaskElement));
if (resultVariableNameString == null) {
resultVariableNameString = businessRuleTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "resultVariableName");
}
if (ruleVariableInputString != null) {
String[] ruleVariableInputObjects = ruleVariableInputString.split(",");
for (String ruleVariableInputObject : ruleVariableInputObjects) {
ruleActivity.addRuleVariableInputIdExpression(expressionManager.createExpression(ruleVariableInputObject.trim()));
}
}
if (rulesString != null) {
String[] rules = rulesString.split(",");
for (String rule : rules) {
ruleActivity.addRuleIdExpression(expressionManager.createExpression(rule.trim()));
}
if (excludeString != null) {
excludeString = excludeString.trim();
if ("true".equalsIgnoreCase(excludeString) == false && "false".equalsIgnoreCase(excludeString) == false) {
addError("'exclude' only supports true or false for business rule tasks", businessRuleTaskElement);
} else {
ruleActivity.setExclude(Boolean.valueOf(excludeString.toLowerCase()));
}
}
} else if (excludeString != null) {
addError("'exclude' not supported for business rule tasks not defining 'rules'", businessRuleTaskElement);
}
if (resultVariableNameString != null) {
resultVariableNameString = resultVariableNameString.trim();
if (resultVariableNameString.length() > 0 == false) {
addError("'resultVariable' must contain a text value for business rule tasks", businessRuleTaskElement);
} else {
ruleActivity.setResultVariable(resultVariableNameString);
}
} else {
ruleActivity.setResultVariable("org.activiti.engine.rules.OUTPUT");
}
activity.setActivityBehavior(ruleActivity);
parseExecutionListenersOnScope(businessRuleTaskElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseBusinessRuleTask(businessRuleTaskElement, scope, activity);
}
return activity;
}
}
/**
* Parses a sendTask declaration.
*/
public ActivityImpl parseSendTask(Element sendTaskElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(sendTaskElement, scope);
activity.setAsync(isAsync(sendTaskElement));
activity.setExclusive(isExclusive(sendTaskElement));
// for e-mail
String type = sendTaskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "type");
// for web service
String implementation = sendTaskElement.attribute("implementation");
String operationRef = this.resolveName(sendTaskElement.attribute("operationRef"));
// for e-mail
if (type != null) {
if (type.equalsIgnoreCase("mail")) {
parseEmailServiceTask(activity, sendTaskElement, parseFieldDeclarations(sendTaskElement));
} else if (type.equalsIgnoreCase("mule")) {
parseMuleServiceTask(activity, sendTaskElement, parseFieldDeclarations(sendTaskElement));
} else {
addError("Invalid usage of type attribute: '" + type + "'", sendTaskElement);
}
// for web service
} else if (implementation != null && operationRef != null && implementation.equalsIgnoreCase("##WebService")) {
if (!this.operations.containsKey(operationRef)) {
addError(operationRef + " does not exist", sendTaskElement);
} else {
Operation operation = this.operations.get(operationRef);
WebServiceActivityBehavior webServiceActivityBehavior = new WebServiceActivityBehavior(operation);
Element ioSpecificationElement = sendTaskElement.element("ioSpecification");
if (ioSpecificationElement != null) {
IOSpecification ioSpecification = this.parseIOSpecification(ioSpecificationElement);
webServiceActivityBehavior.setIoSpecification(ioSpecification);
}
for (Element dataAssociationElement : sendTaskElement.elements("dataInputAssociation")) {
AbstractDataAssociation dataAssociation = this.parseDataInputAssociation(dataAssociationElement);
webServiceActivityBehavior.addDataInputAssociation(dataAssociation);
}
for (Element dataAssociationElement : sendTaskElement.elements("dataOutputAssociation")) {
AbstractDataAssociation dataAssociation = this.parseDataOutputAssociation(dataAssociationElement);
webServiceActivityBehavior.addDataOutputAssociation(dataAssociation);
}
activity.setActivityBehavior(webServiceActivityBehavior);
}
} else {
addError("One of the attributes 'type' or 'operation' is mandatory on sendTask.", sendTaskElement);
}
parseExecutionListenersOnScope(sendTaskElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseSendTask(sendTaskElement, scope, activity);
}
return activity;
}
protected AbstractDataAssociation parseDataOutputAssociation(Element dataAssociationElement) {
String targetRef = dataAssociationElement.element("targetRef").getText();
if (dataAssociationElement.element("sourceRef") != null) {
String sourceRef = dataAssociationElement.element("sourceRef").getText();
return new MessageImplicitDataOutputAssociation(targetRef, sourceRef);
} else {
Expression transformation = this.expressionManager.createExpression(dataAssociationElement.element("transformation").getText());
AbstractDataAssociation dataOutputAssociation = new TransformationDataOutputAssociation(null, targetRef, transformation);
return dataOutputAssociation;
}
}
protected void parseMuleServiceTask(ActivityImpl activity, Element serviceTaskElement, List<FieldDeclaration> fieldDeclarations) {
try {
Class< ? > theClass = Class.forName("org.activiti.mule.MuleSendActivitiBehavior");
activity.setActivityBehavior((ActivityBehavior) ClassDelegate.instantiateDelegate(theClass, fieldDeclarations));
} catch (ClassNotFoundException e) {
addError("Could not find org.activiti.mule.MuleSendActivitiBehavior", serviceTaskElement);
}
}
protected void parseEmailServiceTask(ActivityImpl activity, Element serviceTaskElement, List<FieldDeclaration> fieldDeclarations) {
validateFieldDeclarationsForEmail(serviceTaskElement, fieldDeclarations);
activity.setActivityBehavior((MailActivityBehavior) ClassDelegate.instantiateDelegate(MailActivityBehavior.class, fieldDeclarations));
}
protected void parseShellServiceTask(ActivityImpl activity, Element serviceTaskElement, List<FieldDeclaration> fieldDeclarations) {
validateFieldDeclarationsForShell(serviceTaskElement, fieldDeclarations);
activity.setActivityBehavior((ActivityBehavior) ClassDelegate.instantiateDelegate(ShellActivityBehavior.class, fieldDeclarations));
}
protected void validateFieldDeclarationsForEmail(Element serviceTaskElement, List<FieldDeclaration> fieldDeclarations) {
boolean toDefined = false;
boolean textOrHtmlDefined = false;
for (FieldDeclaration fieldDeclaration : fieldDeclarations) {
if (fieldDeclaration.getName().equals("to")) {
toDefined = true;
}
if (fieldDeclaration.getName().equals("html")) {
textOrHtmlDefined = true;
}
if (fieldDeclaration.getName().equals("text")) {
textOrHtmlDefined = true;
}
}
if (!toDefined) {
addError("No recipient is defined on the mail activity", serviceTaskElement);
}
if (!textOrHtmlDefined) {
addError("Text or html field should be provided", serviceTaskElement);
}
}
protected void validateFieldDeclarationsForShell(Element serviceTaskElement, List<FieldDeclaration> fieldDeclarations) {
boolean shellCommandDefined = false;
for (FieldDeclaration fieldDeclaration : fieldDeclarations) {
String fieldName = fieldDeclaration.getName();
FixedValue fieldFixedValue = (FixedValue) fieldDeclaration.getValue();
String fieldValue = fieldFixedValue.getExpressionText();
shellCommandDefined |= fieldName.equals("command");
if ((fieldName.equals("wait") || fieldName.equals("redirectError") || fieldName.equals("cleanEnv")) && !fieldValue.toLowerCase().equals("true")
&& !fieldValue.toLowerCase().equals("false")) {
addError("undefined value for shell " + fieldName + " parameter :" + fieldValue.toString(), serviceTaskElement);
}
}
if (!shellCommandDefined) {
addError("No shell command is defined on the shell activity", serviceTaskElement);
}
}
public List<FieldDeclaration> parseFieldDeclarations(Element element) {
List<FieldDeclaration> fieldDeclarations = new ArrayList<FieldDeclaration>();
Element elementWithFieldInjections = element.element("extensionElements");
if (elementWithFieldInjections == null) { // Custom extensions will just
// have the <field.. as a
// subelement
elementWithFieldInjections = element;
}
List<Element> fieldDeclarationElements = elementWithFieldInjections.elementsNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "field");
if (fieldDeclarationElements != null && !fieldDeclarationElements.isEmpty()) {
for (Element fieldDeclarationElement : fieldDeclarationElements) {
FieldDeclaration fieldDeclaration = parseFieldDeclaration(element, fieldDeclarationElement);
if (fieldDeclaration != null) {
fieldDeclarations.add(fieldDeclaration);
}
}
}
return fieldDeclarations;
}
protected FieldDeclaration parseFieldDeclaration(Element serviceTaskElement, Element fieldDeclarationElement) {
String fieldName = fieldDeclarationElement.attribute("name");
FieldDeclaration fieldDeclaration = parseStringFieldDeclaration(fieldDeclarationElement, serviceTaskElement, fieldName);
if (fieldDeclaration == null) {
fieldDeclaration = parseExpressionFieldDeclaration(fieldDeclarationElement, serviceTaskElement, fieldName);
}
if (fieldDeclaration == null) {
addError("One of the following is mandatory on a field declaration: one of attributes stringValue|expression "
+ "or one of child elements string|expression", serviceTaskElement);
}
return fieldDeclaration;
}
protected FieldDeclaration parseStringFieldDeclaration(Element fieldDeclarationElement, Element serviceTaskElement, String fieldName) {
try {
String fieldValue = getStringValueFromAttributeOrElement("stringValue", "string", fieldDeclarationElement);
if (fieldValue != null) {
return new FieldDeclaration(fieldName, Expression.class.getName(), new FixedValue(fieldValue));
}
} catch (ActivitiException ae) {
if (ae.getMessage().contains("multiple elements with tag name")) {
addError("Multiple string field declarations found", serviceTaskElement);
} else {
addError("Error when paring field declarations: " + ae.getMessage(), serviceTaskElement);
}
}
return null;
}
protected FieldDeclaration parseExpressionFieldDeclaration(Element fieldDeclarationElement, Element serviceTaskElement, String fieldName) {
try {
String expression = getStringValueFromAttributeOrElement("expression", "expression", fieldDeclarationElement);
if (expression != null && expression.trim().length() > 0) {
return new FieldDeclaration(fieldName, Expression.class.getName(), expressionManager.createExpression(expression));
}
} catch (ActivitiException ae) {
if (ae.getMessage().contains("multiple elements with tag name")) {
addError("Multiple expression field declarations found", serviceTaskElement);
} else {
addError("Error when paring field declarations: " + ae.getMessage(), serviceTaskElement);
}
}
return null;
}
protected String getStringValueFromAttributeOrElement(String attributeName, String elementName, Element element) {
String value = null;
String attributeValue = element.attribute(attributeName);
Element childElement = element.elementNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, elementName);
String stringElementText = null;
if (attributeValue != null && childElement != null) {
addError("Can't use attribute '" + attributeName + "' and element '" + elementName + "' together, only use one", element);
} else if (childElement != null) {
stringElementText = childElement.getText();
if (stringElementText == null || stringElementText.length() == 0) {
addError("No valid value found in attribute '" + attributeName + "' nor element '" + elementName + "'", element);
} else {
// Use text of element
value = stringElementText;
}
} else if (attributeValue != null && attributeValue.length() > 0) {
// Using attribute
value = attributeValue;
}
return value;
}
/**
* Parses a task with no specific type (behaves as passthrough).
*/
public ActivityImpl parseTask(Element taskElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(taskElement, scope);
activity.setActivityBehavior(new TaskActivityBehavior());
activity.setAsync(isAsync(taskElement));
activity.setExclusive(isExclusive(taskElement));
parseExecutionListenersOnScope(taskElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseTask(taskElement, scope, activity);
}
return activity;
}
/**
* Parses a manual task.
*/
public ActivityImpl parseManualTask(Element manualTaskElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(manualTaskElement, scope);
activity.setActivityBehavior(new ManualTaskActivityBehavior());
parseExecutionListenersOnScope(manualTaskElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseManualTask(manualTaskElement, scope, activity);
}
return activity;
}
/**
* Parses a receive task.
*/
public ActivityImpl parseReceiveTask(Element receiveTaskElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(receiveTaskElement, scope);
activity.setActivityBehavior(new ReceiveTaskActivityBehavior());
activity.setAsync(isAsync(receiveTaskElement));
activity.setExclusive(isExclusive(receiveTaskElement));
parseExecutionListenersOnScope(receiveTaskElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseReceiveTask(receiveTaskElement, scope, activity);
}
return activity;
}
/* userTask specific finals */
protected static final String HUMAN_PERFORMER = "humanPerformer";
protected static final String POTENTIAL_OWNER = "potentialOwner";
protected static final String RESOURCE_ASSIGNMENT_EXPR = "resourceAssignmentExpression";
protected static final String FORMAL_EXPRESSION = "formalExpression";
protected static final String USER_PREFIX = "user(";
protected static final String GROUP_PREFIX = "group(";
protected static final String ASSIGNEE_EXTENSION = "assignee";
protected static final String CANDIDATE_USERS_EXTENSION = "candidateUsers";
protected static final String CANDIDATE_GROUPS_EXTENSION = "candidateGroups";
protected static final String DUE_DATE_EXTENSION = "dueDate";
protected static final String PRIORITY_EXTENSION = "priority";
/**
* Parses a userTask declaration.
*/
public ActivityImpl parseUserTask(Element userTaskElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(userTaskElement, scope);
activity.setAsync(isAsync(userTaskElement));
activity.setExclusive(isExclusive(userTaskElement));
TaskDefinition taskDefinition = parseTaskDefinition(userTaskElement, activity.getId(), (ProcessDefinitionEntity) scope.getProcessDefinition());
UserTaskActivityBehavior userTaskActivity = new UserTaskActivityBehavior(expressionManager, taskDefinition);
activity.setActivityBehavior(userTaskActivity);
parseProperties(userTaskElement, activity);
parseExecutionListenersOnScope(userTaskElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseUserTask(userTaskElement, scope, activity);
}
return activity;
}
public TaskDefinition parseTaskDefinition(Element taskElement, String taskDefinitionKey, ProcessDefinitionEntity processDefinition) {
TaskFormHandler taskFormHandler;
String taskFormHandlerClassName = taskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "formHandlerClass");
if (taskFormHandlerClassName != null) {
taskFormHandler = (TaskFormHandler) ReflectUtil.instantiate(taskFormHandlerClassName);
} else {
taskFormHandler = new DefaultTaskFormHandler();
}
taskFormHandler.parseConfiguration(taskElement, deployment, processDefinition, this);
TaskDefinition taskDefinition = new TaskDefinition(taskFormHandler);
taskDefinition.setKey(taskDefinitionKey);
processDefinition.getTaskDefinitions().put(taskDefinitionKey, taskDefinition);
String name = taskElement.attribute("name");
if (name != null) {
taskDefinition.setNameExpression(expressionManager.createExpression(name));
}
String descriptionStr = parseDocumentation(taskElement);
if (descriptionStr != null) {
taskDefinition.setDescriptionExpression(expressionManager.createExpression(descriptionStr));
}
parseHumanPerformer(taskElement, taskDefinition);
parsePotentialOwner(taskElement, taskDefinition);
// Activiti custom extension
parseUserTaskCustomExtensions(taskElement, taskDefinition);
return taskDefinition;
}
protected void parseHumanPerformer(Element taskElement, TaskDefinition taskDefinition) {
List<Element> humanPerformerElements = taskElement.elements(HUMAN_PERFORMER);
if (humanPerformerElements.size() > 1) {
addError("Invalid task definition: multiple " + HUMAN_PERFORMER + " sub elements defined for " + taskDefinition.getNameExpression(), taskElement);
} else if (humanPerformerElements.size() == 1) {
Element humanPerformerElement = humanPerformerElements.get(0);
if (humanPerformerElement != null) {
parseHumanPerformerResourceAssignment(humanPerformerElement, taskDefinition);
}
}
}
protected void parsePotentialOwner(Element taskElement, TaskDefinition taskDefinition) {
List<Element> potentialOwnerElements = taskElement.elements(POTENTIAL_OWNER);
for (Element potentialOwnerElement : potentialOwnerElements) {
parsePotentialOwnerResourceAssignment(potentialOwnerElement, taskDefinition);
}
}
protected void parseHumanPerformerResourceAssignment(Element performerElement, TaskDefinition taskDefinition) {
Element raeElement = performerElement.element(RESOURCE_ASSIGNMENT_EXPR);
if (raeElement != null) {
Element feElement = raeElement.element(FORMAL_EXPRESSION);
if (feElement != null) {
taskDefinition.setAssigneeExpression(expressionManager.createExpression(feElement.getText()));
}
}
}
protected void parsePotentialOwnerResourceAssignment(Element performerElement, TaskDefinition taskDefinition) {
Element raeElement = performerElement.element(RESOURCE_ASSIGNMENT_EXPR);
if (raeElement != null) {
Element feElement = raeElement.element(FORMAL_EXPRESSION);
if (feElement != null) {
List<String> assignmentExpressions = parseCommaSeparatedList(feElement.getText());
for (String assignmentExpression : assignmentExpressions) {
assignmentExpression = assignmentExpression.trim();
if (assignmentExpression.startsWith(USER_PREFIX)) {
String userAssignementId = getAssignmentId(assignmentExpression, USER_PREFIX);
taskDefinition.addCandidateUserIdExpression(expressionManager.createExpression(userAssignementId));
} else if (assignmentExpression.startsWith(GROUP_PREFIX)) {
String groupAssignementId = getAssignmentId(assignmentExpression, GROUP_PREFIX);
taskDefinition.addCandidateGroupIdExpression(expressionManager.createExpression(groupAssignementId));
} else { // default: given string is a goupId, as-is.
taskDefinition.addCandidateGroupIdExpression(expressionManager.createExpression(assignmentExpression));
}
}
}
}
}
protected String getAssignmentId(String expression, String prefix) {
return expression.substring(prefix.length(), expression.length() - 1).trim();
}
protected void parseUserTaskCustomExtensions(Element taskElement, TaskDefinition taskDefinition) {
// assignee
String assignee = taskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, ASSIGNEE_EXTENSION);
if (assignee != null) {
if (taskDefinition.getAssigneeExpression() == null) {
taskDefinition.setAssigneeExpression(expressionManager.createExpression(assignee));
} else {
addError("Invalid usage: duplicate assignee declaration for task " + taskDefinition.getNameExpression(), taskElement);
}
}
// Candidate users
String candidateUsersString = taskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, CANDIDATE_USERS_EXTENSION);
if (candidateUsersString != null) {
List<String> candidateUsers = parseCommaSeparatedList(candidateUsersString);
for (String candidateUser : candidateUsers) {
taskDefinition.addCandidateUserIdExpression(expressionManager.createExpression(candidateUser.trim()));
}
}
// Candidate groups
String candidateGroupsString = taskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, CANDIDATE_GROUPS_EXTENSION);
if (candidateGroupsString != null) {
List<String> candidateGroups = parseCommaSeparatedList(candidateGroupsString);
for (String candidateGroup : candidateGroups) {
taskDefinition.addCandidateGroupIdExpression(expressionManager.createExpression(candidateGroup.trim()));
}
}
// Task listeners
parseTaskListeners(taskElement, taskDefinition);
// Due date
String dueDateExpression = taskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, DUE_DATE_EXTENSION);
if (dueDateExpression != null) {
taskDefinition.setDueDateExpression(expressionManager.createExpression(dueDateExpression));
}
// Priority
final String priorityExpression = taskElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, PRIORITY_EXTENSION);
if (priorityExpression != null) {
taskDefinition.setPriorityExpression(expressionManager.createExpression(priorityExpression));
}
}
/**
* Parses the given String as a list of comma separated entries, where an
* entry can possibly be an expression that has comma's.
*
* If somebody is smart enough to write a regex for this, please let us know.
*
* @return the entries of the comma separated list, trimmed.
*/
protected List<String> parseCommaSeparatedList(String s) {
List<String> result = new ArrayList<String>();
if (s != null && !"".equals(s)) {
StringCharacterIterator iterator = new StringCharacterIterator(s);
char c = iterator.first();
StringBuilder strb = new StringBuilder();
boolean insideExpression = false;
while (c != StringCharacterIterator.DONE) {
if (c == '{' || c == '$') {
insideExpression = true;
} else if (c == '}') {
insideExpression = false;
} else if (c == ',' && !insideExpression) {
result.add(strb.toString().trim());
strb.delete(0, strb.length());
}
if (c != ',' || (insideExpression)) {
strb.append(c);
}
c = iterator.next();
}
if (strb.length() > 0) {
result.add(strb.toString().trim());
}
}
return result;
}
protected void parseTaskListeners(Element userTaskElement, TaskDefinition taskDefinition) {
Element extentionsElement = userTaskElement.element("extensionElements");
if (extentionsElement != null) {
List<Element> taskListenerElements = extentionsElement.elementsNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "taskListener");
for (Element taskListenerElement : taskListenerElements) {
String eventName = taskListenerElement.attribute("event");
if (eventName != null) {
if (TaskListener.EVENTNAME_CREATE.equals(eventName) || TaskListener.EVENTNAME_ASSIGNMENT.equals(eventName)
|| TaskListener.EVENTNAME_COMPLETE.equals(eventName)) {
TaskListener taskListener = parseTaskListener(taskListenerElement);
taskDefinition.addTaskListener(eventName, taskListener);
} else {
addError("Invalid eventName for taskListener: choose 'create' |'assignment'", userTaskElement);
}
} else {
addError("Event is mandatory on taskListener", userTaskElement);
}
}
}
}
protected TaskListener parseTaskListener(Element taskListenerElement) {
TaskListener taskListener = null;
String className = taskListenerElement.attribute("class");
String expression = taskListenerElement.attribute("expression");
String delegateExpression = taskListenerElement.attribute("delegateExpression");
if (className != null) {
taskListener = new ClassDelegate(className, parseFieldDeclarations(taskListenerElement));
} else if (expression != null) {
taskListener = new ExpressionTaskListener(expressionManager.createExpression(expression));
} else if (delegateExpression != null) {
taskListener = new DelegateExpressionTaskListener(expressionManager.createExpression(delegateExpression));
} else {
addError("Element 'class' or 'expression' is mandatory on taskListener", taskListenerElement);
}
return taskListener;
}
/**
* Parses the end events of a certain level in the process (process,
* subprocess or another scope).
*
* @param parentElement
* The 'parent' element that contains the end events (process,
* subprocess).
* @param scope
* The {@link ScopeImpl} to which the end events must be added.
*/
public void parseEndEvents(Element parentElement, ScopeImpl scope) {
for (Element endEventElement : parentElement.elements("endEvent")) {
ActivityImpl activity = createActivityOnScope(endEventElement, scope);
Element errorEventDefinition = endEventElement.element("errorEventDefinition");
Element cancelEventDefinition = endEventElement.element("cancelEventDefinition");
if (errorEventDefinition != null) { // error end event
String errorRef = errorEventDefinition.attribute("errorRef");
if (errorRef == null || "".equals(errorRef)) {
addError("'errorRef' attribute is mandatory on error end event", errorEventDefinition);
} else {
Error error = errors.get(errorRef);
activity.setProperty("type", "errorEndEvent");
activity.setActivityBehavior(new ErrorEndEventActivityBehavior(error != null ? error.getErrorCode() : errorRef));
}
} else if (cancelEventDefinition != null) {
if(scope.getProperty("type")==null || !scope.getProperty("type").equals("transaction")) {
addError("end event with cancelEventDefinition only supported inside transaction subprocess", cancelEventDefinition);
} else {
activity.setProperty("type", "cancelEndEvent");
activity.setActivityBehavior(new CancelEndEventActivityBehavior());
}
} else { // default: none end event
activity.setActivityBehavior(new NoneEndEventActivityBehavior());
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseEndEvent(endEventElement, scope, activity);
}
parseExecutionListenersOnScope(endEventElement, activity);
}
}
/**
* Parses the boundary events of a certain 'level' (process, subprocess or
* other scope).
*
* Note that the boundary events are not parsed during the parsing of the bpmn
* activities, since the semantics are different (boundaryEvent needs to be
* added as nested activity to the reference activity on PVM level).
*
* @param parentElement
* The 'parent' element that contains the activities (process,
* subprocess).
* @param scopeElement
* The {@link ScopeImpl} to which the activities must be added.
*/
public void parseBoundaryEvents(Element parentElement, ScopeImpl scopeElement) {
for (Element boundaryEventElement : parentElement.elements("boundaryEvent")) {
// The boundary event is attached to an activity, reference by the
// 'attachedToRef' attribute
String attachedToRef = boundaryEventElement.attribute("attachedToRef");
if (attachedToRef == null || attachedToRef.equals("")) {
addError("AttachedToRef is required when using a timerEventDefinition", boundaryEventElement);
}
// Representation structure-wise is a nested activity in the activity to
// which its attached
String id = boundaryEventElement.attribute("id");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Parsing boundary event " + id);
}
ActivityImpl parentActivity = scopeElement.findActivity(attachedToRef);
if (parentActivity == null) {
addError("Invalid reference in boundary event. Make sure that the referenced activity is " + "defined in the same scope as the boundary event",
boundaryEventElement);
}
ActivityImpl nestedActivity = createActivityOnScope(boundaryEventElement, parentActivity);
String cancelActivity = boundaryEventElement.attribute("cancelActivity", "true");
boolean interrupting = cancelActivity.equals("true") ? true : false;
// Catch event behavior is the same for most types
ActivityBehavior behavior = new BoundaryEventActivityBehavior(interrupting, nestedActivity.getId());
// Depending on the sub-element definition, the correct activityBehavior
// parsing is selected
Element timerEventDefinition = boundaryEventElement.element("timerEventDefinition");
Element errorEventDefinition = boundaryEventElement.element("errorEventDefinition");
Element signalEventDefinition = boundaryEventElement.element("signalEventDefinition");
Element cancelEventDefinition = boundaryEventElement.element("cancelEventDefinition");
Element compensateEventDefinition = boundaryEventElement.element("compensateEventDefinition");
if (timerEventDefinition != null) {
parseBoundaryTimerEventDefinition(timerEventDefinition, interrupting, nestedActivity);
} else if (errorEventDefinition != null) {
interrupting = true; // non-interrupting not yet supported
parseBoundaryErrorEventDefinition(errorEventDefinition, interrupting, parentActivity, nestedActivity);
} else if (signalEventDefinition != null) {
parseBoundarySignalEventDefinition(signalEventDefinition, interrupting, nestedActivity);
} else if (cancelEventDefinition != null) {
// always interrupting
behavior = parseBoundaryCancelEventDefinition(cancelEventDefinition, nestedActivity);
} else if(compensateEventDefinition != null) {
parseCatchCompensateEventDefinition(compensateEventDefinition, nestedActivity);
} else {
addError("Unsupported boundary event type", boundaryEventElement);
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseBoundaryEvent(boundaryEventElement, scopeElement, nestedActivity);
}
nestedActivity.setActivityBehavior(behavior);
}
}
/**
* Parses a boundary timer event. The end-result will be that the given nested
* activity will get the appropriate {@link ActivityBehavior}.
*
* @param timerEventDefinition
* The XML element corresponding with the timer event details
* @param interrupting
* Indicates whether this timer is interrupting.
* @param timerActivity
* The activity which maps to the structure of the timer event on the
* boundary of another activity. Note that this is NOT the activity
* onto which the boundary event is attached, but a nested activity
* inside this activity, specifically created for this event.
*/
public void parseBoundaryTimerEventDefinition(Element timerEventDefinition, boolean interrupting, ActivityImpl timerActivity) {
timerActivity.setProperty("type", "boundaryTimer");
TimerDeclarationImpl timerDeclaration = parseTimer(timerEventDefinition, timerActivity, TimerExecuteNestedActivityJobHandler.TYPE);
addTimerDeclaration(timerActivity.getParent(), timerDeclaration);
if (timerActivity.getParent() instanceof ActivityImpl) {
((ActivityImpl) timerActivity.getParent()).setScope(true);
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseBoundaryTimerEventDefinition(timerEventDefinition, interrupting, timerActivity);
}
}
public void parseBoundarySignalEventDefinition(Element signalEventDefinition, boolean interrupting, ActivityImpl signalActivity) {
signalActivity.setProperty("type", "boundarySignal");
SignalEventDefinition signalDefinition = parseSignalEventDefinition(signalEventDefinition);
if(signalActivity.getId() == null) {
addError("boundary event has no id", signalEventDefinition);
}
signalDefinition.setActivityId(signalActivity.getId());
addSignalDefinition(signalActivity.getParent(), signalDefinition);
if (signalActivity.getParent() instanceof ActivityImpl) {
((ActivityImpl) signalActivity.getParent()).setScope(true);
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseBoundarySignalEventDefinition(signalEventDefinition, interrupting, signalActivity);
}
}
@SuppressWarnings("unchecked")
private void parseTimerStartEventDefinition(Element timerEventDefinition, ActivityImpl timerActivity, ProcessDefinitionEntity processDefinition) {
timerActivity.setProperty("type", "startTimerEvent");
TimerDeclarationImpl timerDeclaration = parseTimer(timerEventDefinition, timerActivity, TimerStartEventJobHandler.TYPE);
timerDeclaration.setJobHandlerConfiguration(processDefinition.getKey());
List<TimerDeclarationImpl> timerDeclarations = (List<TimerDeclarationImpl>) processDefinition.getProperty(PROPERTYNAME_START_TIMER);
if (timerDeclarations == null) {
timerDeclarations = new ArrayList<TimerDeclarationImpl>();
processDefinition.setProperty(PROPERTYNAME_START_TIMER, timerDeclarations);
}
timerDeclarations.add(timerDeclaration);
}
protected void parseIntemediateSignalEventDefinition(Element signalEventDefinition, ActivityImpl signalActivity, boolean isAfterEventBasedGateway) {
signalActivity.setProperty("type", "intermediateSignalCatch");
SignalEventDefinition signalDefinition = parseSignalEventDefinition(signalEventDefinition);
signalDefinition.setActivityId(signalActivity.getId());
if(isAfterEventBasedGateway) {
addSignalDefinition(signalActivity.getParent(), signalDefinition);
}else {
addSignalDefinition(signalActivity, signalDefinition);
signalActivity.setScope(true);
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseIntermediateSignalCatchEventDefinition(signalEventDefinition, signalActivity);
}
}
@SuppressWarnings("unchecked")
protected void addSignalDefinition(ScopeImpl scopeImpl, SignalEventDefinition signalDefinition) {
List<SignalEventDefinition> signalDefinitions = (List<SignalEventDefinition>) scopeImpl.getProperty(PROPERTYNAME_SIGNAL_DEFINITION_NAME);
if(signalDefinitions == null) {
signalDefinitions = new ArrayList<SignalEventDefinition>();
scopeImpl.setProperty(PROPERTYNAME_SIGNAL_DEFINITION_NAME, signalDefinitions);
}
signalDefinitions.add(signalDefinition);
}
protected SignalEventDefinition parseSignalEventDefinition(Element signalEventDefinitionElement) {
String signalRef = signalEventDefinitionElement.attribute("signalRef");
if (signalRef == null) {
addError("signalEventDefinition does not have required property 'signalRef'", signalEventDefinitionElement);
return null;
} else {
SignalDefinition signalDefinition = signals.get(signalRef);
if (signalDefinition == null) {
addError("Could not find signal with id '" + signalRef + "'", signalEventDefinitionElement);
}
SignalEventDefinition signalEventDefinition = new SignalEventDefinition(signalDefinition);
boolean asynch = "true".equals(signalEventDefinitionElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "async", "false"));
signalEventDefinition.setAsync(asynch);
return signalEventDefinition;
}
}
private void parseIntemediateTimerEventDefinition(Element timerEventDefinition, ActivityImpl timerActivity, boolean isAfterEventBasedGateway) {
timerActivity.setProperty("type", "intermediateTimer");
TimerDeclarationImpl timerDeclaration = parseTimer(timerEventDefinition, timerActivity, TimerCatchIntermediateEventJobHandler.TYPE);
if(isAfterEventBasedGateway) {
addTimerDeclaration(timerActivity.getParent(), timerDeclaration);
}else {
addTimerDeclaration(timerActivity, timerDeclaration);
timerActivity.setScope(true);
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseIntermediateTimerEventDefinition(timerEventDefinition, timerActivity);
}
}
private TimerDeclarationImpl parseTimer(Element timerEventDefinition, ScopeImpl timerActivity, String jobHandlerType) {
// TimeDate
TimerDeclarationType type = TimerDeclarationType.DATE;
Expression expression = parseExpression(timerEventDefinition, "timeDate");
// TimeCycle
if (expression == null) {
type = TimerDeclarationType.CYCLE;
expression = parseExpression(timerEventDefinition, "timeCycle");
}
// TimeDuration
if (expression == null) {
type = TimerDeclarationType.DURATION;
expression = parseExpression(timerEventDefinition, "timeDuration");
}
// neither date, cycle or duration configured!
if (expression==null) {
addError("Timer needs configuration (either timeDate, timeCycle or timeDuration is needed).", timerEventDefinition);
}
// Parse the timer declaration
// TODO move the timer declaration into the bpmn activity or next to the
// TimerSession
TimerDeclarationImpl timerDeclaration = new TimerDeclarationImpl(expression, type, jobHandlerType);
timerDeclaration.setJobHandlerConfiguration(timerActivity.getId());
timerDeclaration.setExclusive("true".equals(timerEventDefinition.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "exclusive", String.valueOf(JobEntity.DEFAULT_EXCLUSIVE))));
return timerDeclaration;
}
private Expression parseExpression(Element parent, String name) {
Element value = parent.element(name);
if (value != null) {
String expressionText = value.getText().trim();
return expressionManager.createExpression(expressionText);
}
return null;
}
public void parseBoundaryErrorEventDefinition(Element errorEventDefinition, boolean interrupting, ActivityImpl activity, ActivityImpl nestedErrorEventActivity) {
nestedErrorEventActivity.setProperty("type", "boundaryError");
ScopeImpl catchingScope = nestedErrorEventActivity.getParent();
((ActivityImpl) catchingScope).setScope(true);
String errorRef = errorEventDefinition.attribute("errorRef");
Error error = null;
ErrorEventDefinition definition = new ErrorEventDefinition(nestedErrorEventActivity.getId());
if (errorRef != null) {
error = errors.get(errorRef);
definition.setErrorCode(error == null ? errorRef : error.getErrorCode());
}
addErrorEventDefinition(definition, catchingScope);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseBoundaryErrorEventDefinition(errorEventDefinition, interrupting, activity, nestedErrorEventActivity);
}
}
protected void addErrorEventDefinition(ErrorEventDefinition errorEventDefinition, ScopeImpl catchingScope) {
List<ErrorEventDefinition> errorEventDefinitions = (List<ErrorEventDefinition>) catchingScope.getProperty(PROPERTYNAME_ERROR_EVENT_DEFINITIONS);
if(errorEventDefinitions == null) {
errorEventDefinitions = new ArrayList<ErrorEventDefinition>();
catchingScope.setProperty(PROPERTYNAME_ERROR_EVENT_DEFINITIONS, errorEventDefinitions);
}
errorEventDefinitions.add(errorEventDefinition);
Collections.sort(errorEventDefinitions, ErrorEventDefinition.comparator);
}
protected List<ActivityImpl> getAllChildActivitiesOfType(String type, ScopeImpl scope) {
List<ActivityImpl> children = new ArrayList<ActivityImpl>();
for (ActivityImpl childActivity : scope.getActivities()) {
if (type.equals(childActivity.getProperty("type"))) {
children.add(childActivity);
}
children.addAll(getAllChildActivitiesOfType(type, childActivity));
}
return children;
}
/**
* Checks if the given activity is a child activity of the
* possibleParentActivity.
*/
protected boolean isChildActivity(ActivityImpl activityToCheck, ActivityImpl possibleParentActivity) {
for (ActivityImpl child : possibleParentActivity.getActivities()) {
if (child.getId().equals(activityToCheck.getId()) || isChildActivity(activityToCheck, child)) {
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
protected void addTimerDeclaration(ScopeImpl scope, TimerDeclarationImpl timerDeclaration) {
List<TimerDeclarationImpl> timerDeclarations = (List<TimerDeclarationImpl>) scope.getProperty(PROPERTYNAME_TIMER_DECLARATION);
if (timerDeclarations == null) {
timerDeclarations = new ArrayList<TimerDeclarationImpl>();
scope.setProperty(PROPERTYNAME_TIMER_DECLARATION, timerDeclarations);
}
timerDeclarations.add(timerDeclaration);
}
@SuppressWarnings("unchecked")
protected void addVariableDeclaration(ScopeImpl scope, VariableDeclaration variableDeclaration) {
List<VariableDeclaration> variableDeclarations = (List<VariableDeclaration>) scope.getProperty(PROPERTYNAME_VARIABLE_DECLARATIONS);
if (variableDeclarations == null) {
variableDeclarations = new ArrayList<VariableDeclaration>();
scope.setProperty(PROPERTYNAME_VARIABLE_DECLARATIONS, variableDeclarations);
}
variableDeclarations.add(variableDeclaration);
}
/**
* Parses a subprocess (formally known as an embedded subprocess): a subprocess
* defined within another process definition.
*
* @param subProcessElement
* The XML element corresponding with the subprocess definition
* @param scope
* The current scope on which the subprocess is defined.
*/
public ActivityImpl parseSubProcess(Element subProcessElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(subProcessElement, scope);
activity.setAsync(isAsync(subProcessElement));
activity.setExclusive(isExclusive(subProcessElement));
Boolean isTriggeredByEvent = parseBooleanAttribute(subProcessElement.attribute("triggeredByEvent"), false);
activity.setProperty("triggeredByEvent", isTriggeredByEvent);
// event subprocesses are not scopes
activity.setScope(!isTriggeredByEvent);
activity.setActivityBehavior(new SubProcessActivityBehavior());
parseScope(subProcessElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseSubProcess(subProcessElement, scope, activity);
}
return activity;
}
private ActivityImpl parseTransaction(Element transactionElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(transactionElement, scope);
activity.setAsync(isAsync(transactionElement));
activity.setExclusive(isExclusive(transactionElement));
activity.setScope(true);
activity.setActivityBehavior(new TransactionActivityBehavior());
parseScope(transactionElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseTransaction(transactionElement, scope, activity);
}
return activity;
}
/**
* Parses a call activity (currently only supporting calling subprocesses).
*
* @param callActivityElement
* The XML element defining the call activity
* @param scope
* The current scope on which the call activity is defined.
*/
public ActivityImpl parseCallActivity(Element callActivityElement, ScopeImpl scope) {
ActivityImpl activity = createActivityOnScope(callActivityElement, scope);
activity.setAsync(isAsync(callActivityElement));
activity.setExclusive(isExclusive(callActivityElement));
String calledElement = callActivityElement.attribute("calledElement");
if (calledElement == null) {
addError("Missing attribute 'calledElement'", callActivityElement);
}
CallActivityBehavior callActivityBehaviour = null;
String expressionRegex = "\\$+\\{+.+\\}";
if (calledElement.matches(expressionRegex)) {
callActivityBehaviour = new CallActivityBehavior(expressionManager.createExpression(calledElement));
} else {
callActivityBehaviour = new CallActivityBehavior(calledElement);
}
Element extentionsElement = callActivityElement.element("extensionElements");
if (extentionsElement != null) {
// input data elements
for (Element listenerElement : extentionsElement.elementsNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "in")) {
String sourceExpression = listenerElement.attribute("sourceExpression");
String target = listenerElement.attribute("target");
if (sourceExpression != null) {
Expression expression = expressionManager.createExpression(sourceExpression.trim());
callActivityBehaviour.addDataInputAssociation(new SimpleDataInputAssociation(expression, target));
} else {
String source = listenerElement.attribute("source");
callActivityBehaviour.addDataInputAssociation(new SimpleDataInputAssociation(source, target));
}
}
// output data elements
for (Element listenerElement : extentionsElement.elementsNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "out")) {
String sourceExpression = listenerElement.attribute("sourceExpression");
String target = listenerElement.attribute("target");
if (sourceExpression != null) {
Expression expression = expressionManager.createExpression(sourceExpression.trim());
callActivityBehaviour.addDataOutputAssociation(new MessageImplicitDataOutputAssociation(target, expression));
} else {
String source = listenerElement.attribute("source");
callActivityBehaviour.addDataOutputAssociation(new MessageImplicitDataOutputAssociation(target, source));
}
}
}
// // parse data input and output
// for (Element dataAssociationElement :
// callActivityElement.elements("dataInputAssociation")) {
// AbstractDataAssociation dataAssociation =
// this.parseDataInputAssociation(dataAssociationElement);
// callActivityBehaviour.addDataInputAssociation(dataAssociation);
// }
//
// for (Element dataAssociationElement :
// callActivityElement.elements("dataOutputAssociation")) {
// AbstractDataAssociation dataAssociation =
// this.parseDataOutputAssociation(dataAssociationElement);
// callActivityBehaviour.addDataOutputAssociation(dataAssociation);
// }
activity.setScope(true);
activity.setActivityBehavior(callActivityBehaviour);
parseExecutionListenersOnScope(callActivityElement, activity);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseCallActivity(callActivityElement, scope, activity);
}
return activity;
}
/**
* Parses the properties of an element (if any) that can contain properties
* (processes, activities, etc.)
*
* Returns true if property subelemens are found.
*
* @param element
* The element that can contain properties.
* @param activity
* The activity where the property declaration is done.
*/
public void parseProperties(Element element, ActivityImpl activity) {
List<Element> propertyElements = element.elements("property");
for (Element propertyElement : propertyElements) {
parseProperty(propertyElement, activity);
}
}
/**
* Parses one property definition.
*
* @param propertyElement
* The 'property' element that defines how a property looks like and
* is handled.
*/
public void parseProperty(Element propertyElement, ActivityImpl activity) {
String id = propertyElement.attribute("id");
String name = propertyElement.attribute("name");
// If name isn't given, use the id as name
if (name == null) {
if (id == null) {
addError("Invalid property usage on line " + propertyElement.getLine() + ": no id or name specified.", propertyElement);
} else {
name = id;
}
}
String itemSubjectRef = propertyElement.attribute("itemSubjectRef");
String type = null;
if (itemSubjectRef != null) {
ItemDefinition itemDefinition = itemDefinitions.get(itemSubjectRef);
if (itemDefinition != null) {
StructureDefinition structure = itemDefinition.getStructureDefinition();
type = structure.getId();
} else {
addError("Invalid itemDefinition reference: " + itemSubjectRef + " not found", propertyElement);
}
}
parsePropertyCustomExtensions(activity, propertyElement, name, type);
}
/**
* Parses the custom extensions for properties.
*
* @param activity
* The activity where the property declaration is done.
* @param propertyElement
* The 'property' element defining the property.
* @param propertyName
* The name of the property.
* @param propertyType
* The type of the property.
*/
public void parsePropertyCustomExtensions(ActivityImpl activity, Element propertyElement, String propertyName, String propertyType) {
if (propertyType == null) {
String type = propertyElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "type");
propertyType = type != null ? type : "string"; // default is string
}
VariableDeclaration variableDeclaration = new VariableDeclaration(propertyName, propertyType);
addVariableDeclaration(activity, variableDeclaration);
activity.setScope(true);
String src = propertyElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "src");
if (src != null) {
variableDeclaration.setSourceVariableName(src);
}
String srcExpr = propertyElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "srcExpr");
if (srcExpr != null) {
Expression sourceExpression = expressionManager.createExpression(srcExpr);
variableDeclaration.setSourceExpression(sourceExpression);
}
String dst = propertyElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "dst");
if (dst != null) {
variableDeclaration.setDestinationVariableName(dst);
}
String destExpr = propertyElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "dstExpr");
if (destExpr != null) {
Expression destinationExpression = expressionManager.createExpression(destExpr);
variableDeclaration.setDestinationExpression(destinationExpression);
}
String link = propertyElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "link");
if (link != null) {
variableDeclaration.setLink(link);
}
String linkExpr = propertyElement.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "linkExpr");
if (linkExpr != null) {
Expression linkExpression = expressionManager.createExpression(linkExpr);
variableDeclaration.setLinkExpression(linkExpression);
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseProperty(propertyElement, variableDeclaration, activity);
}
}
/**
* Parses all sequence flow of a scope.
*
* @param processElement
* The 'process' element wherein the sequence flow are defined.
* @param scope
* The scope to which the sequence flow must be added.
*/
public void parseSequenceFlow(Element processElement, ScopeImpl scope) {
for (Element sequenceFlowElement : processElement.elements("sequenceFlow")) {
String id = sequenceFlowElement.attribute("id");
String sourceRef = sequenceFlowElement.attribute("sourceRef");
String destinationRef = sequenceFlowElement.attribute("targetRef");
// Implicit check: sequence flow cannot cross (sub) process boundaries: we
// don't do a processDefinition.findActivity here
ActivityImpl sourceActivity = scope.findActivity(sourceRef);
ActivityImpl destinationActivity = scope.findActivity(destinationRef);
if (sourceActivity == null) {
addError("Invalid source '" + sourceRef + "' of sequence flow '" + id + "'", sequenceFlowElement);
} else if (destinationActivity == null) {
addError("Invalid destination '" + destinationRef + "' of sequence flow '" + id + "'", sequenceFlowElement);
} else if(sourceActivity.getActivityBehavior() instanceof EventBasedGatewayActivityBehavior) {
// ignore
} else if(destinationActivity.getActivityBehavior() instanceof IntermediateCatchEventActivitiBehaviour
&& (destinationActivity.getParentActivity() != null)
&& (destinationActivity.getParentActivity().getActivityBehavior() instanceof EventBasedGatewayActivityBehavior)) {
addError("Invalid incoming sequenceflow for intermediateCatchEvent with id '"+destinationActivity.getId()+"' connected to an event-based gateway.", sequenceFlowElement);
} else {
TransitionImpl transition = sourceActivity.createOutgoingTransition(id);
sequenceFlows.put(id, transition);
transition.setProperty("name", sequenceFlowElement.attribute("name"));
transition.setProperty("documentation", parseDocumentation(sequenceFlowElement));
transition.setDestination(destinationActivity);
parseSequenceFlowConditionExpression(sequenceFlowElement, transition);
parseExecutionListenersOnTransition(sequenceFlowElement, transition);
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseSequenceFlow(sequenceFlowElement, scope, transition);
}
}
}
}
/**
* Parses a condition expression on a sequence flow.
*
* @param seqFlowElement
* The 'sequenceFlow' element that can contain a condition.
* @param seqFlow
* The sequenceFlow object representation to which the condition must
* be added.
*/
public void parseSequenceFlowConditionExpression(Element seqFlowElement, TransitionImpl seqFlow) {
Element conditionExprElement = seqFlowElement.element("conditionExpression");
if (conditionExprElement != null) {
String expression = conditionExprElement.getText().trim();
String type = conditionExprElement.attributeNS(BpmnParser.XSI_NS, "type");
if (type != null && !type.equals("tFormalExpression")) {
addError("Invalid type, only tFormalExpression is currently supported", conditionExprElement);
}
Condition expressionCondition = new UelExpressionCondition(expressionManager.createExpression(expression));
seqFlow.setProperty(PROPERTYNAME_CONDITION_TEXT, expression);
seqFlow.setProperty(PROPERTYNAME_CONDITION, expressionCondition);
}
}
/**
* Parses all execution-listeners on a scope.
*
* @param scopeElement
* the XML element containing the scope definition.
* @param scope
* the scope to add the executionListeners to.
* @param postProcessActivities
*/
public void parseExecutionListenersOnScope(Element scopeElement, ScopeImpl scope) {
Element extentionsElement = scopeElement.element("extensionElements");
if (extentionsElement != null) {
List<Element> listenerElements = extentionsElement.elementsNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "executionListener");
for (Element listenerElement : listenerElements) {
String eventName = listenerElement.attribute("event");
if (isValidEventNameForScope(eventName, listenerElement)) {
ExecutionListener listener = parseExecutionListener(listenerElement);
if (listener != null) {
scope.addExecutionListener(eventName, listener);
}
}
}
}
}
/**
* Check if the given event name is valid. If not, an appropriate error is
* added.
*/
protected boolean isValidEventNameForScope(String eventName, Element listenerElement) {
if (eventName != null && eventName.trim().length() > 0) {
if ("start".equals(eventName) || "end".equals(eventName)) {
return true;
} else {
addError("Attribute 'eventName' must be one of {start|end}", listenerElement);
}
} else {
addError("Attribute 'eventName' is mandatory on listener", listenerElement);
}
return false;
}
public void parseExecutionListenersOnTransition(Element activitiElement, TransitionImpl activity) {
Element extentionsElement = activitiElement.element("extensionElements");
if (extentionsElement != null) {
List<Element> listenerElements = extentionsElement.elementsNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "executionListener");
for (Element listenerElement : listenerElements) {
ExecutionListener listener = parseExecutionListener(listenerElement);
if (listener != null) {
// Since a transition only fires event 'take', we don't parse the
// eventName, it is ignored
activity.addExecutionListener(listener);
}
}
}
}
/**
* Parses an {@link ExecutionListener} implementation for the given
* executionListener element.
*
* @param executionListenerElement
* the XML element containing the executionListener definition.
*/
public ExecutionListener parseExecutionListener(Element executionListenerElement) {
ExecutionListener executionListener = null;
String className = executionListenerElement.attribute("class");
String expression = executionListenerElement.attribute("expression");
String delegateExpression = executionListenerElement.attribute("delegateExpression");
if (className != null) {
executionListener = new ClassDelegate(className, parseFieldDeclarations(executionListenerElement));
} else if (expression != null) {
executionListener = new ExpressionExecutionListener(expressionManager.createExpression(expression));
} else if (delegateExpression != null) {
executionListener = new DelegateExpressionExecutionListener(expressionManager.createExpression(delegateExpression));
} else {
addError("Element 'class' or 'expression' is mandatory on executionListener", executionListenerElement);
}
return executionListener;
}
/**
* Retrieves the {@link Operation} corresponding with the given operation
* identifier.
*/
public Operation getOperation(String operationId) {
return operations.get(operationId);
}
// Diagram interchange
// /////////////////////////////////////////////////////////////////
public void parseDiagramInterchangeElements() {
// Multiple BPMNDiagram possible
List<Element> diagrams = rootElement.elementsNS(BpmnParser.BPMN_DI_NS, "BPMNDiagram");
if (!diagrams.isEmpty()) {
for (Element diagramElement : diagrams) {
parseBPMNDiagram(diagramElement);
}
}
}
public void parseBPMNDiagram(Element bpmndiagramElement) {
// Each BPMNdiagram needs to have exactly one BPMNPlane
Element bpmnPlane = bpmndiagramElement.elementNS(BpmnParser.BPMN_DI_NS, "BPMNPlane");
if (bpmnPlane != null) {
parseBPMNPlane(bpmnPlane);
}
}
public void parseBPMNPlane(Element bpmnPlaneElement) {
String bpmnElement = bpmnPlaneElement.attribute("bpmnElement");
if (bpmnElement != null && !"".equals(bpmnElement)) {
// there seems to be only on process without collaboration
if (getProcessDefinition(bpmnElement) != null) {
getProcessDefinition(bpmnElement).setGraphicalNotationDefined(true);
}
List<Element> shapes = bpmnPlaneElement.elementsNS(BpmnParser.BPMN_DI_NS, "BPMNShape");
for (Element shape : shapes) {
parseBPMNShape(shape);
}
List<Element> edges = bpmnPlaneElement.elementsNS(BpmnParser.BPMN_DI_NS, "BPMNEdge");
for (Element edge : edges) {
parseBPMNEdge(edge);
}
} else {
addError("'bpmnElement' attribute is required on BPMNPlane ", bpmnPlaneElement);
}
}
public void parseBPMNShape(Element bpmnShapeElement) {
String bpmnElement = bpmnShapeElement.attribute("bpmnElement");
if (bpmnElement != null && !"".equals(bpmnElement)) {
// For collaborations, their are also shape definitions for the
// participants / processes
if (participantProcesses.get(bpmnElement) != null) {
ProcessDefinitionEntity procDef = getProcessDefinition(participantProcesses.get(bpmnElement));
procDef.setGraphicalNotationDefined(true);
// The participation that references this process, has a bounds to be rendered + a name as wel
parseDIBounds(bpmnShapeElement, procDef.getParticipantProcess());
return;
}
for (ProcessDefinitionEntity processDefinition : getProcessDefinitions()) {
ActivityImpl activity = processDefinition.findActivity(bpmnElement);
if (activity != null) {
parseDIBounds(bpmnShapeElement, activity);
// collapsed or expanded
String isExpanded = bpmnShapeElement.attribute("isExpanded");
if (isExpanded != null) {
activity.setProperty(PROPERTYNAME_ISEXPANDED, parseBooleanAttribute(isExpanded));
}
} else {
Lane lane = processDefinition.getLaneForId(bpmnElement);
if(lane != null) {
// The shape represents a lane
parseDIBounds(bpmnShapeElement, lane);
} else if(!elementIds.contains(bpmnElement)) { // It might not be an activity nor a lane, but it might still reference 'something'
addError("Invalid reference in 'bpmnElement' attribute, activity " + bpmnElement + "not found", bpmnShapeElement);
}
}
}
} else {
addError("'bpmnElement' attribute is required on BPMNShape", bpmnShapeElement);
}
}
protected void parseDIBounds(Element bpmnShapeElement, HasDIBounds target) {
Element bounds = bpmnShapeElement.elementNS(BpmnParser.BPMN_DC_NS, "Bounds");
if (bounds != null) {
target.setX(parseDoubleAttribute(bpmnShapeElement, "x", bounds.attribute("x"), true).intValue());
target.setY(parseDoubleAttribute(bpmnShapeElement, "y", bounds.attribute("y"), true).intValue());
target.setWidth(parseDoubleAttribute(bpmnShapeElement, "width", bounds.attribute("width"), true).intValue());
target.setHeight(parseDoubleAttribute(bpmnShapeElement, "height", bounds.attribute("height"), true).intValue());
} else {
addError("'Bounds' element is required", bpmnShapeElement);
}
}
public void parseBPMNEdge(Element bpmnEdgeElement) {
String sequenceFlowId = bpmnEdgeElement.attribute("bpmnElement");
if (sequenceFlowId != null && !"".equals(sequenceFlowId)) {
TransitionImpl sequenceFlow = sequenceFlows.get(sequenceFlowId);
if (sequenceFlow != null) {
List<Element> waypointElements = bpmnEdgeElement.elementsNS(BpmnParser.OMG_DI_NS, "waypoint");
if (waypointElements.size() >= 2) {
List<Integer> waypoints = new ArrayList<Integer>();
for (Element waypointElement : waypointElements) {
waypoints.add(parseDoubleAttribute(waypointElement, "x", waypointElement.attribute("x"), true).intValue());
waypoints.add(parseDoubleAttribute(waypointElement, "y", waypointElement.attribute("y"), true).intValue());
}
sequenceFlow.setWaypoints(waypoints);
} else {
addError("Minimum 2 waypoint elements must be definted for a 'BPMNEdge'", bpmnEdgeElement);
}
} else if(!elementIds.contains(sequenceFlowId)) { // it might not be a sequenceFlow but it might still reference 'something'
addError("Invalid reference in 'bpmnElement' attribute, sequenceFlow " + sequenceFlowId + "not found", bpmnEdgeElement);
}
} else {
addError("'bpmnElement' attribute is required on BPMNEdge", bpmnEdgeElement);
}
}
// Getters, setters and Parser overriden operations
// ////////////////////////////////////////
public List<ProcessDefinitionEntity> getProcessDefinitions() {
return processDefinitions;
}
public ProcessDefinitionEntity getProcessDefinition(String processDefinitionKey) {
for (ProcessDefinitionEntity processDefinition : processDefinitions) {
if (processDefinition.getKey().equals(processDefinitionKey)) {
return processDefinition;
}
}
return null;
}
@Override
public BpmnParse name(String name) {
super.name(name);
return this;
}
@Override
public BpmnParse sourceInputStream(InputStream inputStream) {
super.sourceInputStream(inputStream);
return this;
}
@Override
public BpmnParse sourceResource(String resource, ClassLoader classLoader) {
super.sourceResource(resource, classLoader);
return this;
}
@Override
public BpmnParse sourceResource(String resource) {
super.sourceResource(resource);
return this;
}
@Override
public BpmnParse sourceString(String string) {
super.sourceString(string);
return this;
}
@Override
public BpmnParse sourceUrl(String url) {
super.sourceUrl(url);
return this;
}
@Override
public BpmnParse sourceUrl(URL url) {
super.sourceUrl(url);
return this;
}
public void addStructure(StructureDefinition structure) {
this.structures.put(structure.getId(), structure);
}
public void addService(BpmnInterfaceImplementation bpmnInterfaceImplementation) {
this.interfaceImplementations.put(bpmnInterfaceImplementation.getName(), bpmnInterfaceImplementation);
}
public void addOperation(OperationImplementation operationImplementation) {
this.operationImplementations.put(operationImplementation.getId(), operationImplementation);
}
public Boolean parseBooleanAttribute(String booleanText, boolean defaultValue) {
if (booleanText == null) {
return defaultValue;
} else {
return parseBooleanAttribute(booleanText);
}
}
public Boolean parseBooleanAttribute(String booleanText) {
if ("true".equals(booleanText) || "enabled".equals(booleanText) || "on".equals(booleanText) || "active".equals(booleanText) || "yes".equals(booleanText)) {
return Boolean.TRUE;
}
if ("false".equals(booleanText) || "disabled".equals(booleanText) || "off".equals(booleanText) || "inactive".equals(booleanText)
|| "no".equals(booleanText)) {
return Boolean.FALSE;
}
return null;
}
public Double parseDoubleAttribute(Element element, String attributename, String doubleText, boolean required) {
if (required && (doubleText == null || "".equals(doubleText))) {
addError(attributename + " is required", element);
} else {
try {
return Double.parseDouble(doubleText);
} catch (NumberFormatException e) {
addError("Cannot parse " + attributename + ": " + e.getMessage(), element);
}
}
return -1.0;
}
protected boolean isExclusive(Element element) {
return "true".equals(element.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "exclusive", String.valueOf(JobEntity.DEFAULT_EXCLUSIVE)));
}
protected boolean isAsync(Element element) {
return "true".equals(element.attributeNS(BpmnParser.ACTIVITI_BPMN_EXTENSIONS_NS, "async"));
}
}