mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-01-15 12:44:52 +00:00
7c219fd731
git-subtree-dir: SpiffWorkflow git-subtree-split: 63db3e45947ec66b8d0efc2c74064004f8ff482c
232 lines
10 KiB
Python
232 lines
10 KiB
Python
from lxml import etree
|
|
|
|
from SpiffWorkflow.bpmn.specs.events.event_definitions import CorrelationProperty
|
|
|
|
from .ValidationException import ValidationException
|
|
from .TaskParser import TaskParser
|
|
from .util import first, one
|
|
from ..specs.events import (TimerEventDefinition, MessageEventDefinition,
|
|
ErrorEventDefinition, EscalationEventDefinition,
|
|
SignalEventDefinition,
|
|
CancelEventDefinition, CycleTimerEventDefinition,
|
|
TerminateEventDefinition, NoneEventDefinition)
|
|
|
|
|
|
CAMUNDA_MODEL_NS = 'http://camunda.org/schema/1.0/bpmn'
|
|
CANCEL_EVENT_XPATH = './/bpmn:cancelEventDefinition'
|
|
ERROR_EVENT_XPATH = './/bpmn:errorEventDefinition'
|
|
ESCALATION_EVENT_XPATH = './/bpmn:escalationEventDefinition'
|
|
TERMINATION_EVENT_XPATH = './/bpmn:terminateEventDefinition'
|
|
MESSAGE_EVENT_XPATH = './/bpmn:messageEventDefinition'
|
|
SIGNAL_EVENT_XPATH = './/bpmn:signalEventDefinition'
|
|
TIMER_EVENT_XPATH = './/bpmn:timerEventDefinition'
|
|
|
|
class EventDefinitionParser(TaskParser):
|
|
"""This class provvides methods for parsing different event definitions."""
|
|
|
|
def parse_cancel_event(self):
|
|
return CancelEventDefinition()
|
|
|
|
def parse_error_event(self, error_event):
|
|
"""Parse the errorEventDefinition node and return an instance of ErrorEventDefinition."""
|
|
error_ref = error_event.get('errorRef')
|
|
if error_ref:
|
|
error = one(self.doc_xpath('.//bpmn:error[@id="%s"]' % error_ref))
|
|
error_code = error.get('errorCode')
|
|
name = error.get('name')
|
|
else:
|
|
name, error_code = 'None Error Event', None
|
|
return ErrorEventDefinition(name, error_code)
|
|
|
|
def parse_escalation_event(self, escalation_event):
|
|
"""Parse the escalationEventDefinition node and return an instance of EscalationEventDefinition."""
|
|
|
|
escalation_ref = escalation_event.get('escalationRef')
|
|
if escalation_ref:
|
|
escalation = one(self.doc_xpath('.//bpmn:escalation[@id="%s"]' % escalation_ref))
|
|
escalation_code = escalation.get('escalationCode')
|
|
name = escalation.get('name')
|
|
else:
|
|
name, escalation_code = 'None Escalation Event', None
|
|
return EscalationEventDefinition(name, escalation_code)
|
|
|
|
def parse_message_event(self, message_event):
|
|
|
|
message_ref = message_event.get('messageRef')
|
|
if message_ref is not None:
|
|
message = one(self.doc_xpath('.//bpmn:message[@id="%s"]' % message_ref))
|
|
name = message.get('name')
|
|
correlations = self.get_message_correlations(message_ref)
|
|
else:
|
|
name = message_event.getparent().get('name')
|
|
correlations = {}
|
|
return MessageEventDefinition(name, correlations)
|
|
|
|
def parse_signal_event(self, signal_event):
|
|
"""Parse the signalEventDefinition node and return an instance of SignalEventDefinition."""
|
|
|
|
signal_ref = signal_event.get('signalRef')
|
|
if signal_ref:
|
|
signal = one(self.doc_xpath('.//bpmn:signal[@id="%s"]' % signal_ref))
|
|
name = signal.get('name')
|
|
else:
|
|
name = signal_event.getparent().get('name')
|
|
return SignalEventDefinition(name)
|
|
|
|
def parse_terminate_event(self):
|
|
"""Parse the terminateEventDefinition node and return an instance of TerminateEventDefinition."""
|
|
return TerminateEventDefinition()
|
|
|
|
def parse_timer_event(self):
|
|
"""Parse the timerEventDefinition node and return an instance of TimerEventDefinition."""
|
|
|
|
try:
|
|
time_date = first(self.xpath('.//bpmn:timeDate'))
|
|
if time_date is not None:
|
|
return TimerEventDefinition(self.node.get('name'), time_date.text)
|
|
|
|
time_duration = first(self.xpath('.//bpmn:timeDuration'))
|
|
if time_duration is not None:
|
|
return TimerEventDefinition(self.node.get('name'), time_duration.text)
|
|
|
|
time_cycle = first(self.xpath('.//bpmn:timeCycle'))
|
|
if time_cycle is not None:
|
|
return CycleTimerEventDefinition(self.node.get('name'), time_cycle.text)
|
|
raise ValidationException("Unknown Time Specification", node=self.node, filename=self.filename)
|
|
except Exception as e:
|
|
raise ValidationException("Time Specification Error. " + str(e), node=self.node, filename=self.filename)
|
|
|
|
def get_message_correlations(self, message_ref):
|
|
|
|
correlations = []
|
|
for correlation in self.doc_xpath(f".//bpmn:correlationPropertyRetrievalExpression[@messageRef='{message_ref}']"):
|
|
key = correlation.getparent().get('id')
|
|
children = correlation.getchildren()
|
|
expression = children[0].text if len(children) > 0 else None
|
|
used_by = [ e.getparent().get('name') for e in
|
|
self.doc_xpath(f".//bpmn:correlationKey/bpmn:correlationPropertyRef[text()='{key}']") ]
|
|
if key is not None and expression is not None:
|
|
correlations.append(CorrelationProperty(key, expression, used_by))
|
|
return correlations
|
|
|
|
def _create_task(self, event_definition, cancel_activity=None):
|
|
|
|
if isinstance(event_definition, MessageEventDefinition):
|
|
for prop in event_definition.correlation_properties:
|
|
for key in prop.correlation_keys:
|
|
if key not in self.spec.correlation_keys:
|
|
self.spec.correlation_keys[key] = []
|
|
if prop.name not in self.spec.correlation_keys[key]:
|
|
self.spec.correlation_keys[key].append(prop.name)
|
|
|
|
kwargs = {
|
|
'lane': self.lane,
|
|
'description': self.node.get('name', None),
|
|
'position': self.position,
|
|
}
|
|
if cancel_activity is not None:
|
|
kwargs['cancel_activity'] = cancel_activity
|
|
return self.spec_class(self.spec, self.get_task_spec_name(), event_definition, **kwargs)
|
|
|
|
def get_event_definition(self, xpaths):
|
|
"""Returns the first event definition it can find in given list of xpaths"""
|
|
for path in xpaths:
|
|
event = first(self.xpath(path))
|
|
if event is not None:
|
|
if path == MESSAGE_EVENT_XPATH:
|
|
return self.parse_message_event(event)
|
|
elif path == SIGNAL_EVENT_XPATH:
|
|
return self.parse_signal_event(event)
|
|
elif path == TIMER_EVENT_XPATH:
|
|
return self.parse_timer_event()
|
|
elif path == CANCEL_EVENT_XPATH:
|
|
return self.parse_cancel_event()
|
|
elif path == ERROR_EVENT_XPATH:
|
|
return self.parse_error_event(event)
|
|
elif path == ESCALATION_EVENT_XPATH:
|
|
return self.parse_escalation_event(event)
|
|
elif path == TERMINATION_EVENT_XPATH:
|
|
return self.parse_terminate_event()
|
|
return NoneEventDefinition()
|
|
|
|
class StartEventParser(EventDefinitionParser):
|
|
"""Parses a Start Event, and connects it to the internal spec.start task.
|
|
Support Message, Signal, and Timer events."""
|
|
|
|
def create_task(self):
|
|
event_definition = self.get_event_definition([MESSAGE_EVENT_XPATH, SIGNAL_EVENT_XPATH, TIMER_EVENT_XPATH])
|
|
task = self._create_task(event_definition)
|
|
self.spec.start.connect(task)
|
|
if isinstance(event_definition, CycleTimerEventDefinition):
|
|
# We are misusing cycle timers, so this is a hack whereby we will
|
|
# revisit ourselves if we fire.
|
|
task.connect(task)
|
|
return task
|
|
|
|
def handles_multiple_outgoing(self):
|
|
return True
|
|
|
|
|
|
class EndEventParser(EventDefinitionParser):
|
|
"""Parses an End Event. Handles Termination, Escalation, Cancel, and Error End Events."""
|
|
|
|
def create_task(self):
|
|
event_definition = self.get_event_definition([MESSAGE_EVENT_XPATH, CANCEL_EVENT_XPATH, ERROR_EVENT_XPATH,
|
|
ESCALATION_EVENT_XPATH, TERMINATION_EVENT_XPATH])
|
|
task = self._create_task(event_definition)
|
|
task.connect_outgoing(self.spec.end, '%s.ToEndJoin' % self.node.get('id'), None, None)
|
|
return task
|
|
|
|
|
|
class IntermediateCatchEventParser(EventDefinitionParser):
|
|
"""Parses an Intermediate Catch Event. Currently supports Message, Signal, and Timer definitions."""
|
|
|
|
def create_task(self):
|
|
event_definition = self.get_event_definition([MESSAGE_EVENT_XPATH, SIGNAL_EVENT_XPATH, TIMER_EVENT_XPATH])
|
|
return super()._create_task(event_definition)
|
|
|
|
|
|
class IntermediateThrowEventParser(EventDefinitionParser):
|
|
"""Parses an Intermediate Catch Event. Currently supports Message, Signal and Timer event definitions."""
|
|
|
|
def create_task(self):
|
|
event_definition = self.get_event_definition([ESCALATION_EVENT_XPATH, MESSAGE_EVENT_XPATH,
|
|
SIGNAL_EVENT_XPATH, TIMER_EVENT_XPATH])
|
|
return self._create_task(event_definition)
|
|
|
|
|
|
class SendTaskParser(IntermediateThrowEventParser):
|
|
|
|
def create_task(self):
|
|
|
|
if self.node.get('messageRef') is not None:
|
|
event_definition = self.parse_message_event(self.node)
|
|
else:
|
|
message_event = first(self.xpath(MESSAGE_EVENT_XPATH))
|
|
if message_event is not None:
|
|
event_definition = self.parse_message_event(message_event)
|
|
else:
|
|
event_definition = NoneEventDefinition()
|
|
|
|
return self._create_task(event_definition)
|
|
|
|
|
|
class ReceiveTaskParser(SendTaskParser):
|
|
"""Identical to the SendTaskParser - check for a message event definition"""
|
|
pass
|
|
|
|
|
|
class BoundaryEventParser(EventDefinitionParser):
|
|
"""
|
|
Parse a Catching Boundary Event. This extends the
|
|
IntermediateCatchEventParser in order to parse the event definition.
|
|
"""
|
|
|
|
def create_task(self):
|
|
cancel_activity = self.node.get('cancelActivity', default='true').lower() == 'true'
|
|
event_definition = self.get_event_definition([CANCEL_EVENT_XPATH, ERROR_EVENT_XPATH, ESCALATION_EVENT_XPATH,
|
|
MESSAGE_EVENT_XPATH, SIGNAL_EVENT_XPATH, TIMER_EVENT_XPATH])
|
|
if isinstance(event_definition, NoneEventDefinition):
|
|
raise NotImplementedError('Unsupported Catch Event: %r', etree.tostring(self.node))
|
|
return self._create_task(event_definition, cancel_activity)
|