Squashed 'SpiffWorkflow/' changes from 46d3de27f..ffb168675

ffb168675 Option to run tests in parallel (#271)
062eaf15d another hot match -- assure hit policy is correctly passed through.
c79ee8407 Quick patch the DMN hit policy to fix a dump mistake.
36dd1b23a Fix ResourceWarning: unclosed file BpmnParser.py:60 (#270)
bba7ddf54 Merge pull request #268 from sartography/feature/multiple-event-definition
8cf770985 remove unused import
9d31e035e make multiple throw events work with start events
890c4b921 add throw support for multiple events
c1fc55660 add support for catching parallel multiple event definitions
511830b67 add event based gateway
56bd858dc add event type for multiple events

git-subtree-dir: SpiffWorkflow
git-subtree-split: ffb1686757f944065580dd2db8def73d6c1f0134
This commit is contained in:
burnettk 2022-12-10 23:39:00 -05:00
parent cc1903387b
commit 71a2a3ec0e
22 changed files with 735 additions and 64 deletions

View File

@ -36,8 +36,16 @@ uninstall:
.PHONY : tests
tests:
cd tests/$(NAME)
PYTHONPATH=../.. python -m unittest discover -v . "*Test.py"
python -m unittest discover -vs tests/SpiffWorkflow -p \*Test.py -t .
.PHONY : tests-par
tests-par:
@if ! command -v unittest-parallel >/dev/null 2>&1; then \
echo "unittest-parallel not found. Please install it with:"; \
echo " pip install unittest-parallel"; \
exit 1; \
fi
unittest-parallel --module-fixtures -vs tests/SpiffWorkflow -p \*Test.py -t .
.PHONY : tests-cov
tests-cov:

View File

@ -29,7 +29,7 @@ from .ValidationException import ValidationException
from ..specs.BpmnProcessSpec import BpmnProcessSpec
from ..specs.events.EndEvent import EndEvent
from ..specs.events.StartEvent import StartEvent
from ..specs.events.IntermediateEvent import BoundaryEvent, IntermediateCatchEvent, IntermediateThrowEvent
from ..specs.events.IntermediateEvent import BoundaryEvent, IntermediateCatchEvent, IntermediateThrowEvent, EventBasedGateway
from ..specs.events.IntermediateEvent import SendTask, ReceiveTask
from ..specs.SubWorkflowTask import CallActivity, SubWorkflowTask, TransactionSubprocess
from ..specs.ExclusiveGateway import ExclusiveGateway
@ -47,7 +47,7 @@ from .task_parsers import (UserTaskParser, NoneTaskParser, ManualTaskParser,
ExclusiveGatewayParser, ParallelGatewayParser, InclusiveGatewayParser,
CallActivityParser, ScriptTaskParser, SubWorkflowParser,
ServiceTaskParser)
from .event_parsers import (StartEventParser, EndEventParser, BoundaryEventParser,
from .event_parsers import (EventBasedGatewayParser, StartEventParser, EndEventParser, BoundaryEventParser,
IntermediateCatchEventParser, IntermediateThrowEventParser,
SendTaskParser, ReceiveTaskParser)
@ -57,7 +57,8 @@ XSD_PATH = os.path.join(os.path.dirname(__file__), 'schema', 'BPMN20.xsd')
class BpmnValidator:
def __init__(self, xsd_path=XSD_PATH, imports=None):
schema = etree.parse(open(xsd_path))
with open(xsd_path) as xsd:
schema = etree.parse(xsd)
if imports is not None:
for ns, fn in imports.items():
elem = etree.Element(
@ -104,6 +105,7 @@ class BpmnParser(object):
full_tag('boundaryEvent'): (BoundaryEventParser, BoundaryEvent),
full_tag('receiveTask'): (ReceiveTaskParser, ReceiveTask),
full_tag('sendTask'): (SendTaskParser, SendTask),
full_tag('eventBasedGateway'): (EventBasedGatewayParser, EventBasedGateway),
}
OVERRIDE_PARSER_CLASSES = {}

View File

@ -121,6 +121,25 @@ class TaskParser(NodeParser):
elif len(self.xpath('./bpmn:standardLoopCharacteristics')) > 0:
self._set_multiinstance_attributes(True, 25, STANDARDLOOPCOUNT, loop_task=True)
def _add_boundary_event(self, children):
parent = _BoundaryEventParent(
self.spec, '%s.BoundaryEventParent' % self.get_id(),
self.task, lane=self.task.lane)
self.process_parser.parsed_nodes[self.node.get('id')] = parent
parent.connect_outgoing(self.task, '%s.FromBoundaryEventParent' % self.get_id(), None, None)
for event in children:
child = self.process_parser.parse_node(event)
if isinstance(child.event_definition, CancelEventDefinition) \
and not isinstance(self.task, TransactionSubprocess):
raise ValidationException('Cancel Events may only be used with transactions',
node=self.node,
filename=self.filename)
parent.connect_outgoing(child,
'%s.FromBoundaryEventParent' % event.get('id'),
None, None)
return parent
def parse_node(self):
"""
Parse this node, and all children, returning the connected task spec.
@ -139,30 +158,9 @@ class TaskParser(NodeParser):
boundary_event_nodes = self.doc_xpath('.//bpmn:boundaryEvent[@attachedToRef="%s"]' % self.get_id())
if boundary_event_nodes:
parent_task = _BoundaryEventParent(
self.spec, '%s.BoundaryEventParent' % self.get_id(),
self.task, lane=self.task.lane)
self.process_parser.parsed_nodes[
self.node.get('id')] = parent_task
parent_task.connect_outgoing(
self.task, '%s.FromBoundaryEventParent' % self.get_id(),
None, None)
for boundary_event in boundary_event_nodes:
b = self.process_parser.parse_node(boundary_event)
if isinstance(b.event_definition, CancelEventDefinition) \
and not isinstance(self.task, TransactionSubprocess):
raise ValidationException(
'Cancel Events may only be used with transactions',
node=self.node,
filename=self.filename)
parent_task.connect_outgoing(
b,
'%s.FromBoundaryEventParent' % boundary_event.get(
'id'),
None, None)
parent = self._add_boundary_event(boundary_event_nodes)
else:
self.process_parser.parsed_nodes[
self.node.get('id')] = self.task
self.process_parser.parsed_nodes[self.node.get('id')] = self.task
children = []
outgoing = self.doc_xpath('.//bpmn:sequenceFlow[@sourceRef="%s"]' % self.get_id())
@ -202,7 +200,7 @@ class TaskParser(NodeParser):
c, target_node, sequence_flow,
sequence_flow.get('id') == default_outgoing)
return parent_task if boundary_event_nodes else self.task
return parent if boundary_event_nodes else self.task
except ValidationException:
raise
except Exception as ex:

View File

@ -5,7 +5,7 @@ from SpiffWorkflow.bpmn.specs.events.event_definitions import CorrelationPropert
from .ValidationException import ValidationException
from .TaskParser import TaskParser
from .util import first, one
from ..specs.events.event_definitions import (TimerEventDefinition, MessageEventDefinition,
from ..specs.events.event_definitions import (MultipleEventDefinition, TimerEventDefinition, MessageEventDefinition,
ErrorEventDefinition, EscalationEventDefinition,
SignalEventDefinition,
CancelEventDefinition, CycleTimerEventDefinition,
@ -109,7 +109,7 @@ class EventDefinitionParser(TaskParser):
correlations.append(CorrelationProperty(key, expression, used_by))
return correlations
def _create_task(self, event_definition, cancel_activity=None):
def _create_task(self, event_definition, cancel_activity=None, parallel=None):
if isinstance(event_definition, MessageEventDefinition):
for prop in event_definition.correlation_properties:
@ -126,28 +126,40 @@ class EventDefinitionParser(TaskParser):
}
if cancel_activity is not None:
kwargs['cancel_activity'] = cancel_activity
if parallel is not None:
kwargs['parallel'] = parallel
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"""
"""Returns all event definitions it can find in given list of xpaths"""
event_definitions = []
for path in xpaths:
event = first(self.xpath(path))
if event is not None:
for event in self.xpath(path):
if path == MESSAGE_EVENT_XPATH:
return self.parse_message_event(event)
event_definitions.append(self.parse_message_event(event))
elif path == SIGNAL_EVENT_XPATH:
return self.parse_signal_event(event)
event_definitions.append(self.parse_signal_event(event))
elif path == TIMER_EVENT_XPATH:
return self.parse_timer_event()
event_definitions.append(self.parse_timer_event())
elif path == CANCEL_EVENT_XPATH:
return self.parse_cancel_event()
event_definitions.append(self.parse_cancel_event())
elif path == ERROR_EVENT_XPATH:
return self.parse_error_event(event)
event_definitions.append(self.parse_error_event(event))
elif path == ESCALATION_EVENT_XPATH:
return self.parse_escalation_event(event)
event_definitions.append(self.parse_escalation_event(event))
elif path == TERMINATION_EVENT_XPATH:
return self.parse_terminate_event()
return NoneEventDefinition()
event_definitions.append(self.parse_terminate_event())
parallel = self.node.get('parallelMultiple') == 'true'
if len(event_definitions) == 0:
return NoneEventDefinition()
elif len(event_definitions) == 1:
return event_definitions[0]
else:
return MultipleEventDefinition(event_definitions, parallel)
class StartEventParser(EventDefinitionParser):
"""Parses a Start Event, and connects it to the internal spec.start task.
@ -158,8 +170,7 @@ class StartEventParser(EventDefinitionParser):
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.
# We are misusing cycle timers, so this is a hack whereby we will revisit ourselves if we fire.
task.connect(task)
return task
@ -229,3 +240,22 @@ class BoundaryEventParser(EventDefinitionParser):
if isinstance(event_definition, NoneEventDefinition):
raise NotImplementedError('Unsupported Catch Event: %r', etree.tostring(self.node))
return self._create_task(event_definition, cancel_activity)
class EventBasedGatewayParser(EventDefinitionParser):
def create_task(self):
return self._create_task(MultipleEventDefinition())
def handles_multiple_outgoing(self):
return True
def connect_outgoing(self, outgoing_task, outgoing_task_node, sequence_flow_node, is_default):
self.task.event_definition.event_definitions.append(outgoing_task.event_definition)
self.task.connect_outgoing(
outgoing_task,
sequence_flow_node.get('id'),
sequence_flow_node.get('name', None),
self.parse_documentation(sequence_flow_node)
)

View File

@ -7,7 +7,7 @@ from SpiffWorkflow.bpmn.specs.BpmnProcessSpec import BpmnDataSpecification
from .dictionary import DictionaryConverter
from ..specs.events.event_definitions import SignalEventDefinition, MessageEventDefinition, NoneEventDefinition
from ..specs.events.event_definitions import MultipleEventDefinition, SignalEventDefinition, MessageEventDefinition, NoneEventDefinition
from ..specs.events.event_definitions import TimerEventDefinition, CycleTimerEventDefinition, TerminateEventDefinition
from ..specs.events.event_definitions import ErrorEventDefinition, EscalationEventDefinition, CancelEventDefinition
from ..specs.events.event_definitions import CorrelationProperty, NamedEventDefinition
@ -91,7 +91,7 @@ class BpmnTaskSpecConverter(DictionaryConverter):
event_definitions = [ NoneEventDefinition, CancelEventDefinition, TerminateEventDefinition,
SignalEventDefinition, MessageEventDefinition, ErrorEventDefinition, EscalationEventDefinition,
TimerEventDefinition, CycleTimerEventDefinition ]
TimerEventDefinition, CycleTimerEventDefinition , MultipleEventDefinition]
for event_definition in event_definitions:
self.register(
@ -257,6 +257,9 @@ class BpmnTaskSpecConverter(DictionaryConverter):
dct['error_code'] = event_definition.error_code
if isinstance(event_definition, EscalationEventDefinition):
dct['escalation_code'] = event_definition.escalation_code
if isinstance(event_definition, MultipleEventDefinition):
dct['event_definitions'] = [self.convert(e) for e in event_definition.event_definitions]
dct['parallel'] = event_definition.parallel
return dct
@ -273,6 +276,8 @@ class BpmnTaskSpecConverter(DictionaryConverter):
internal, external = dct.pop('internal'), dct.pop('external')
if 'correlation_properties' in dct:
dct['correlation_properties'] = [CorrelationProperty(**prop) for prop in dct['correlation_properties']]
if 'event_definitions' in dct:
dct['event_definitions'] = [self.restore(d) for d in dct['event_definitions']]
event_definition = definition_class(**dct)
event_definition.internal = internal
event_definition.external = external

View File

@ -21,7 +21,7 @@ from ..specs.ParallelGateway import ParallelGateway
from ..specs.events.StartEvent import StartEvent
from ..specs.events.EndEvent import EndEvent
from ..specs.events.IntermediateEvent import BoundaryEvent, IntermediateCatchEvent, IntermediateThrowEvent
from ..specs.events.IntermediateEvent import BoundaryEvent, EventBasedGateway, IntermediateCatchEvent, IntermediateThrowEvent
from ..specs.events.IntermediateEvent import _BoundaryEventParent, SendTask, ReceiveTask
from ..workflow import BpmnWorkflow
@ -52,6 +52,7 @@ class StartTaskConverter(BpmnTaskSpecConverter):
def from_dict(self, dct):
return self.task_spec_from_dict(dct)
class LoopResetTaskConverter(BpmnTaskSpecConverter):
def __init__(self, data_converter=None, typename=None):
@ -70,6 +71,7 @@ class LoopResetTaskConverter(BpmnTaskSpecConverter):
spec.destination_id = UUID(spec.destination_id)
return spec
class EndJoinConverter(BpmnTaskSpecConverter):
def __init__(self, data_converter=None, typename=None):
@ -310,3 +312,9 @@ class BoundaryEventParentConverter(BpmnTaskSpecConverter):
def from_dict(self, dct):
return self.task_spec_from_dict(dct)
class EventBasedGatewayConverter(EventConverter):
def __init__(self, data_converter=None, typename=None):
super().__init__(EventBasedGateway, data_converter, typename)

View File

@ -17,7 +17,7 @@ from .task_spec_converters import SimpleTaskConverter, StartTaskConverter, EndJo
from .task_spec_converters import NoneTaskConverter, UserTaskConverter, ManualTaskConverter, ScriptTaskConverter
from .task_spec_converters import CallActivityTaskConverter, TransactionSubprocessTaskConverter
from .task_spec_converters import StartEventConverter, EndEventConverter
from .task_spec_converters import IntermediateCatchEventConverter, IntermediateThrowEventConverter
from .task_spec_converters import IntermediateCatchEventConverter, IntermediateThrowEventConverter, EventBasedGatewayConverter
from .task_spec_converters import SendTaskConverter, ReceiveTaskConverter
from .task_spec_converters import BoundaryEventConverter, BoundaryEventParentConverter
from .task_spec_converters import ParallelGatewayConverter, ExclusiveGatewayConverter, InclusiveGatewayConverter
@ -27,7 +27,7 @@ DEFAULT_TASK_SPEC_CONVERTER_CLASSES = [
NoneTaskConverter, UserTaskConverter, ManualTaskConverter, ScriptTaskConverter,
CallActivityTaskConverter, TransactionSubprocessTaskConverter,
StartEventConverter, EndEventConverter, SendTaskConverter, ReceiveTaskConverter,
IntermediateCatchEventConverter, IntermediateThrowEventConverter,
IntermediateCatchEventConverter, IntermediateThrowEventConverter, EventBasedGatewayConverter,
BoundaryEventConverter, BoundaryEventParentConverter,
ParallelGatewayConverter, ExclusiveGatewayConverter, InclusiveGatewayConverter
]

View File

@ -111,6 +111,7 @@ class _BoundaryEventParent(Simple, BpmnSpecMixin):
def deserialize(cls, serializer, wf_spec, s_state):
return serializer.deserialize_boundary_event_parent(wf_spec, s_state, cls)
class BoundaryEvent(CatchingEvent):
"""Task Spec for a bpmn:boundaryEvent node."""
@ -128,7 +129,6 @@ class BoundaryEvent(CatchingEvent):
interrupting = 'Interrupting' if self.cancel_activity else 'Non-Interrupting'
return f'{interrupting} {self.event_definition.event_type} Event'
def catches(self, my_task, event_definition, correlations=None):
# Boundary events should only be caught while waiting
return super(BoundaryEvent, self).catches(my_task, event_definition, correlations) and my_task.state == TaskState.WAITING
@ -148,3 +148,16 @@ class BoundaryEvent(CatchingEvent):
@classmethod
def deserialize(cls, serializer, wf_spec, s_state):
return serializer.deserialize_boundary_event(wf_spec, s_state, cls)
class EventBasedGateway(CatchingEvent):
@property
def spec_type(self):
return 'Event Based Gateway'
def _on_complete_hook(self, my_task):
for child in my_task.children:
if not child.task_spec.event_definition.has_fired(child):
child.cancel()
return super()._on_complete_hook(my_task)

View File

@ -69,10 +69,10 @@ class EventDefinition(object):
# We also don't have a more sophisticated method for addressing events to
# a particular process, but this at least provides a mechanism for distinguishing
# between processes and subprocesses.
if self.internal:
workflow.catch(event)
if self.external:
outer_workflow.catch(event, correlations)
if self.internal and (self.external and workflow != outer_workflow):
workflow.catch(event)
def __eq__(self, other):
return self.__class__.__name__ == other.__class__.__name__
@ -92,6 +92,7 @@ class EventDefinition(object):
obj.internal, obj.external = internal, external
return obj
class NamedEventDefinition(EventDefinition):
"""
Extend the base event class to provide a name for the event. Most throw/catch events
@ -115,7 +116,6 @@ class NamedEventDefinition(EventDefinition):
retdict['name'] = self.name
return retdict
class CancelEventDefinition(EventDefinition):
"""
Cancel events are only handled by the outerworkflow, as they can only be used inside
@ -398,3 +398,49 @@ class CycleTimerEventDefinition(EventDefinition):
retdict['label'] = self.label
retdict['cycle_definition'] = self.cycle_definition
return retdict
class MultipleEventDefinition(EventDefinition):
def __init__(self, event_definitions=None, parallel=False):
super().__init__()
self.event_definitions = event_definitions or []
self.parallel = parallel
@property
def event_type(self):
return 'Multiple'
def catch(self, my_task, event_definition=None):
event_definition.catch(my_task, event_definition)
if self.parallel:
# Parallel multiple need to match all events
seen_events = my_task.internal_data.get('seen_events', []) + [event_definition]
my_task._set_internal_data(seen_events=seen_events)
if all(event in seen_events for event in self.event_definitions):
my_task._set_internal_data(event_fired=True)
else:
my_task._set_internal_data(event_fired=False)
else:
# Otherwise, matching one is sufficient
my_task._set_internal_data(event_fired=True)
def reset(self, my_task):
my_task.internal_data.pop('seen_events', None)
super().reset(my_task)
def __eq__(self, other):
# This event can catch any of the events associated with it
for event in self.event_definitions:
if event == other:
return True
return False
def throw(self, my_task):
# Mutiple events throw all associated events when they fire
for event_definition in self.event_definitions:
self._throw(
event=event_definition,
workflow=my_task.workflow,
outer_workflow=my_task.workflow.outer_workflow
)

View File

@ -16,7 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition
from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition, MultipleEventDefinition
from .PythonScriptEngine import PythonScriptEngine
from .specs.events.event_types import CatchingEvent
from .specs.events.StartEvent import StartEvent
@ -113,6 +113,14 @@ class BpmnWorkflow(Workflow):
workflow = workflow.outer_workflow
return workflow
def _get_or_create_subprocess(self, task_spec, wf_spec):
if isinstance(task_spec.event_definition, MultipleEventDefinition):
for sp in self.subprocesses.values():
start = sp.get_tasks_from_spec_name(task_spec.name)
if len(start) and start[0].state == TaskState.WAITING:
return sp
return self.add_subprocess(wf_spec.name, f'{wf_spec.name}_{len(self.subprocesses)}')
def catch(self, event_definition, correlations=None):
"""
Send an event definition to any tasks that catch it.
@ -134,10 +142,8 @@ class BpmnWorkflow(Workflow):
for name, spec in self.subprocess_specs.items():
for task_spec in list(spec.task_specs.values()):
if isinstance(task_spec, StartEvent) and task_spec.event_definition == event_definition:
subprocess = self.add_subprocess(spec.name, f'{spec.name}_{len(self.subprocesses)}')
subprocess.correlations = correlations or {}
start = self.get_tasks_from_spec_name(task_spec.name, workflow=subprocess)[0]
task_spec.event_definition.catch(start, event_definition)
subprocess = self._get_or_create_subprocess(task_spec, spec)
subprocess.correlations.update(correlations or {})
# We need to get all the tasks that catch an event before completing any of them
# in order to prevent the scenario where multiple boundary events catch the

View File

@ -30,9 +30,7 @@ class DMNEngine:
a given task."""
result = {}
matched_rules = self.decide(task)
if len(matched_rules) == 1:
result = matched_rules[0].output_as_dict(task)
elif len(matched_rules) > 1: # This must be a multi-output
if self.decision_table.hit_policy == HitPolicy.COLLECT.value:
# each output will be an array of values, all outputs will
# be placed in a dict, which we will then merge.
for rule in matched_rules:
@ -41,6 +39,8 @@ class DMNEngine:
if not key in result:
result[key] = []
result[key].append(rule_output[key])
elif len(matched_rules) > 0:
result = matched_rules[0].output_as_dict(task)
return result

View File

@ -21,6 +21,7 @@ class BusinessRuleTaskConverter(BpmnTaskSpecConverter):
return {
'id': table.id,
'name': table.name,
'hit_policy': table.hit_policy,
'inputs': [val.__dict__ for val in table.inputs],
'outputs': [val.__dict__ for val in table.outputs],
'rules': [self.rule_to_dict(rule) for rule in table.rules],

View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1jaorpt" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.11.1" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.15.0">
<bpmn:process id="Process_0pvx19v" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0w4b5t2</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_0w4b5t2" sourceRef="StartEvent_1" targetRef="Gateway_1434v9l" />
<bpmn:eventBasedGateway id="Gateway_1434v9l">
<bpmn:incoming>Flow_0w4b5t2</bpmn:incoming>
<bpmn:outgoing>Flow_0gge7fn</bpmn:outgoing>
<bpmn:outgoing>Flow_0px7ksu</bpmn:outgoing>
<bpmn:outgoing>Flow_1rfbrlf</bpmn:outgoing>
</bpmn:eventBasedGateway>
<bpmn:intermediateCatchEvent id="message_1_event">
<bpmn:incoming>Flow_0gge7fn</bpmn:incoming>
<bpmn:outgoing>Flow_1g4g85l</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_158nhox" messageRef="Message_0lyfmat" />
</bpmn:intermediateCatchEvent>
<bpmn:sequenceFlow id="Flow_0gge7fn" sourceRef="Gateway_1434v9l" targetRef="message_1_event" />
<bpmn:intermediateCatchEvent id="message_2_event">
<bpmn:incoming>Flow_0px7ksu</bpmn:incoming>
<bpmn:outgoing>Flow_18v90rx</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_1w1pnze" messageRef="Message_1ntpwce" />
</bpmn:intermediateCatchEvent>
<bpmn:sequenceFlow id="Flow_0px7ksu" sourceRef="Gateway_1434v9l" targetRef="message_2_event" />
<bpmn:intermediateCatchEvent id="timer_event">
<bpmn:incoming>Flow_1rfbrlf</bpmn:incoming>
<bpmn:outgoing>Flow_0mppjk9</bpmn:outgoing>
<bpmn:timerEventDefinition id="TimerEventDefinition_0reo0gl">
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">timedelta(seconds=1)</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:intermediateCatchEvent>
<bpmn:sequenceFlow id="Flow_1rfbrlf" sourceRef="Gateway_1434v9l" targetRef="timer_event" />
<bpmn:endEvent id="timer">
<bpmn:incoming>Flow_0mppjk9</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0mppjk9" sourceRef="timer_event" targetRef="timer" />
<bpmn:endEvent id="message_1_end">
<bpmn:incoming>Flow_1g4g85l</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1g4g85l" sourceRef="message_1_event" targetRef="message_1_end" />
<bpmn:endEvent id="message_2_end">
<bpmn:incoming>Flow_18v90rx</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_18v90rx" sourceRef="message_2_event" targetRef="message_2_end" />
</bpmn:process>
<bpmn:message id="Message_0lyfmat" name="message_1" />
<bpmn:message id="Message_1ntpwce" name="message_2" />
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0pvx19v">
<bpmndi:BPMNEdge id="Flow_18v90rx_di" bpmnElement="Flow_18v90rx">
<di:waypoint x="408" y="230" />
<di:waypoint x="472" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1g4g85l_di" bpmnElement="Flow_1g4g85l">
<di:waypoint x="408" y="117" />
<di:waypoint x="472" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0mppjk9_di" bpmnElement="Flow_0mppjk9">
<di:waypoint x="408" y="340" />
<di:waypoint x="472" y="340" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1rfbrlf_di" bpmnElement="Flow_1rfbrlf">
<di:waypoint x="290" y="142" />
<di:waypoint x="290" y="340" />
<di:waypoint x="372" y="340" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0px7ksu_di" bpmnElement="Flow_0px7ksu">
<di:waypoint x="290" y="142" />
<di:waypoint x="290" y="230" />
<di:waypoint x="372" y="230" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0gge7fn_di" bpmnElement="Flow_0gge7fn">
<di:waypoint x="315" y="117" />
<di:waypoint x="372" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0w4b5t2_di" bpmnElement="Flow_0w4b5t2">
<di:waypoint x="215" y="117" />
<di:waypoint x="265" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_0gplu2e_di" bpmnElement="Gateway_1434v9l">
<dc:Bounds x="265" y="92" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1og7irs_di" bpmnElement="message_1_event">
<dc:Bounds x="372" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0quxwe6_di" bpmnElement="message_2_event">
<dc:Bounds x="372" y="212" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1ea7gov_di" bpmnElement="timer_event">
<dc:Bounds x="372" y="322" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0emrepu_di" bpmnElement="timer">
<dc:Bounds x="472" y="322" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0x07cac_di" bpmnElement="message_1_end">
<dc:Bounds x="472" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1p7slpj_di" bpmnElement="message_2_end">
<dc:Bounds x="472" y="212" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_19o7vxg" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
<bpmn:process id="main" name="Main" isExecutable="true">
<bpmn:startEvent id="StartEvent_1" parallelMultiple="true">
<bpmn:outgoing>Flow_1tr2mqr</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_158nhox" messageRef="Message_0lyfmat" />
<bpmn:messageEventDefinition id="MessageEventDefinition_1w1pnze" messageRef="Message_1ntpwce" />
</bpmn:startEvent>
<bpmn:task id="any_task" name="Any Task">
<bpmn:incoming>Flow_1tr2mqr</bpmn:incoming>
<bpmn:outgoing>Flow_1qjctmo</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_1tr2mqr" sourceRef="StartEvent_1" targetRef="any_task" />
<bpmn:endEvent id="Event_0hamwsf">
<bpmn:incoming>Flow_1qjctmo</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1qjctmo" sourceRef="any_task" targetRef="Event_0hamwsf" />
</bpmn:process>
<bpmn:message id="Message_0lyfmat" name="message_1" />
<bpmn:message id="Message_1ntpwce" name="message_2" />
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="main">
<bpmndi:BPMNShape id="Event_1mddj6x_di" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="169" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1bn6xph_di" bpmnElement="any_task">
<dc:Bounds x="270" y="147" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0hamwsf_di" bpmnElement="Event_0hamwsf">
<dc:Bounds x="432" y="169" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1tr2mqr_di" bpmnElement="Flow_1tr2mqr">
<di:waypoint x="215" y="187" />
<di:waypoint x="270" y="187" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1qjctmo_di" bpmnElement="Flow_1qjctmo">
<di:waypoint x="370" y="187" />
<di:waypoint x="432" y="187" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_19o7vxg" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
<bpmn:process id="main" name="Main" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1tr2mqr</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_158nhox" messageRef="Message_0lyfmat" />
<bpmn:messageEventDefinition id="MessageEventDefinition_1w1pnze" messageRef="Message_1ntpwce" />
</bpmn:startEvent>
<bpmn:task id="any_task" name="Any Task">
<bpmn:incoming>Flow_1tr2mqr</bpmn:incoming>
<bpmn:outgoing>Flow_1qjctmo</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_1tr2mqr" sourceRef="StartEvent_1" targetRef="any_task" />
<bpmn:endEvent id="Event_0hamwsf">
<bpmn:incoming>Flow_1qjctmo</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1qjctmo" sourceRef="any_task" targetRef="Event_0hamwsf" />
</bpmn:process>
<bpmn:message id="Message_0lyfmat" name="message_1" />
<bpmn:message id="Message_1ntpwce" name="message_2" />
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="main">
<bpmndi:BPMNShape id="Event_1mddj6x_di" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="169" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1bn6xph_di" bpmnElement="any_task">
<dc:Bounds x="270" y="147" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0hamwsf_di" bpmnElement="Event_0hamwsf">
<dc:Bounds x="432" y="169" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1tr2mqr_di" bpmnElement="Flow_1tr2mqr">
<di:waypoint x="215" y="187" />
<di:waypoint x="270" y="187" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1qjctmo_di" bpmnElement="Flow_1qjctmo">
<di:waypoint x="370" y="187" />
<di:waypoint x="432" y="187" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_19o7vxg" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
<bpmn:message id="Message_0lyfmat" name="message_1" />
<bpmn:message id="Message_1ntpwce" name="message_2" />
<bpmn:collaboration id="top">
<bpmn:participant id="responder" name="Responder" processRef="respond" />
<bpmn:participant id="initiator" name="Initiator" processRef="initiate" />
</bpmn:collaboration>
<bpmn:process id="respond" name="Respond" isExecutable="true">
<bpmn:startEvent id="Event_07g2lnb" parallelMultiple="true">
<bpmn:outgoing>Flow_04uk4n8</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_158nhox" messageRef="Message_0lyfmat" />
<bpmn:messageEventDefinition id="MessageEventDefinition_1w1pnze" messageRef="Message_1ntpwce" />
</bpmn:startEvent>
<bpmn:endEvent id="Event_0hamwsf">
<bpmn:incoming>Flow_08al33k</bpmn:incoming>
</bpmn:endEvent>
<bpmn:task id="any_task" name="Any Task">
<bpmn:incoming>Flow_04uk4n8</bpmn:incoming>
<bpmn:outgoing>Flow_08al33k</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_04uk4n8" sourceRef="Event_07g2lnb" targetRef="any_task" />
<bpmn:sequenceFlow id="Flow_08al33k" sourceRef="any_task" targetRef="Event_0hamwsf" />
</bpmn:process>
<bpmn:process id="initiate" name="Initiate" isExecutable="true">
<bpmn:startEvent id="Event_0jamixt">
<bpmn:outgoing>Flow_1wgdi4h</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1wgdi4h" sourceRef="Event_0jamixt" targetRef="Event_0n8a7vh" />
<bpmn:sequenceFlow id="Flow_1wxjn4e" sourceRef="Event_0n8a7vh" targetRef="Event_0vork94" />
<bpmn:endEvent id="Event_0vork94">
<bpmn:incoming>Flow_1wxjn4e</bpmn:incoming>
</bpmn:endEvent>
<bpmn:intermediateThrowEvent id="Event_0n8a7vh">
<bpmn:incoming>Flow_1wgdi4h</bpmn:incoming>
<bpmn:outgoing>Flow_1wxjn4e</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_158nhox_throw" messageRef="Message_0lyfmat" />
<bpmn:messageEventDefinition id="MessageEventDefinition_1w1pnze_throw" messageRef="Message_1ntpwce" />
</bpmn:intermediateThrowEvent>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="top">
<bpmndi:BPMNShape id="Participant_0ctz0ow_di" bpmnElement="responder" isHorizontal="true">
<dc:Bounds x="120" y="62" width="430" height="250" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_07g2lnb_di" bpmnElement="Event_07g2lnb">
<dc:Bounds x="192" y="169" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0hamwsf_di" bpmnElement="Event_0hamwsf">
<dc:Bounds x="432" y="169" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1yaipjy_di" bpmnElement="any_task">
<dc:Bounds x="280" y="147" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_04uk4n8_di" bpmnElement="Flow_04uk4n8">
<di:waypoint x="228" y="187" />
<di:waypoint x="280" y="187" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_08al33k_di" bpmnElement="Flow_08al33k">
<di:waypoint x="380" y="187" />
<di:waypoint x="432" y="187" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Participant_0gyj8ha_di" bpmnElement="initiator" isHorizontal="true">
<dc:Bounds x="120" y="350" width="430" height="250" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0jamixt_di" bpmnElement="Event_0jamixt">
<dc:Bounds x="192" y="452" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0vork94_di" bpmnElement="Event_0vork94">
<dc:Bounds x="432" y="452" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0n8a7vh_di" bpmnElement="Event_0n8a7vh">
<dc:Bounds x="322" y="452" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1wgdi4h_di" bpmnElement="Flow_1wgdi4h">
<di:waypoint x="228" y="470" />
<di:waypoint x="322" y="470" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1wxjn4e_di" bpmnElement="Flow_1wxjn4e">
<di:waypoint x="358" y="470" />
<di:waypoint x="432" y="470" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_19o7vxg" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
<bpmn:message id="Message_0lyfmat" name="message_1" />
<bpmn:message id="Message_1ntpwce" name="message_2" />
<bpmn:collaboration id="top">
<bpmn:participant id="responder" name="Responder" processRef="respond" />
<bpmn:participant id="initiator" name="Initiator" processRef="initiate" />
</bpmn:collaboration>
<bpmn:process id="respond" name="Respond" isExecutable="true">
<bpmn:endEvent id="Event_0hamwsf">
<bpmn:incoming>Flow_1tr2mqr</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1tr2mqr" sourceRef="StartEvent_1" targetRef="Event_0hamwsf" />
<bpmn:intermediateCatchEvent id="StartEvent_1" parallelMultiple="true">
<bpmn:incoming>Flow_1wohnl8</bpmn:incoming>
<bpmn:outgoing>Flow_1tr2mqr</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_158nhox" messageRef="Message_0lyfmat" />
<bpmn:messageEventDefinition id="MessageEventDefinition_1w1pnze" messageRef="Message_1ntpwce" />
</bpmn:intermediateCatchEvent>
<bpmn:startEvent id="Event_07g2lnb">
<bpmn:outgoing>Flow_1wohnl8</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1wohnl8" sourceRef="Event_07g2lnb" targetRef="StartEvent_1" />
</bpmn:process>
<bpmn:process id="initiate" name="Initiate" isExecutable="true">
<bpmn:startEvent id="Event_0jamixt">
<bpmn:outgoing>Flow_1wgdi4h</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1wgdi4h" sourceRef="Event_0jamixt" targetRef="Event_0n8a7vh" />
<bpmn:sequenceFlow id="Flow_1wxjn4e" sourceRef="Event_0n8a7vh" targetRef="Event_0vork94" />
<bpmn:endEvent id="Event_0vork94">
<bpmn:incoming>Flow_1wxjn4e</bpmn:incoming>
</bpmn:endEvent>
<bpmn:intermediateThrowEvent id="Event_0n8a7vh">
<bpmn:incoming>Flow_1wgdi4h</bpmn:incoming>
<bpmn:outgoing>Flow_1wxjn4e</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_158nhox_throw" messageRef="Message_0lyfmat" />
<bpmn:messageEventDefinition id="MessageEventDefinition_1w1pnze_throw" messageRef="Message_1ntpwce" />
</bpmn:intermediateThrowEvent>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="top">
<bpmndi:BPMNShape id="Participant_0ctz0ow_di" bpmnElement="responder" isHorizontal="true">
<dc:Bounds x="120" y="62" width="430" height="250" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0hamwsf_di" bpmnElement="Event_0hamwsf">
<dc:Bounds x="432" y="169" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1mddj6x_di" bpmnElement="StartEvent_1">
<dc:Bounds x="312" y="169" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_07g2lnb_di" bpmnElement="Event_07g2lnb">
<dc:Bounds x="192" y="169" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1tr2mqr_di" bpmnElement="Flow_1tr2mqr">
<di:waypoint x="348" y="187" />
<di:waypoint x="432" y="187" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1wohnl8_di" bpmnElement="Flow_1wohnl8">
<di:waypoint x="228" y="187" />
<di:waypoint x="312" y="187" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Participant_0gyj8ha_di" bpmnElement="initiator" isHorizontal="true">
<dc:Bounds x="120" y="350" width="430" height="250" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0jamixt_di" bpmnElement="Event_0jamixt">
<dc:Bounds x="192" y="452" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0vork94_di" bpmnElement="Event_0vork94">
<dc:Bounds x="432" y="452" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0n8a7vh_di" bpmnElement="Event_0n8a7vh">
<dc:Bounds x="322" y="452" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1wgdi4h_di" bpmnElement="Flow_1wgdi4h">
<di:waypoint x="228" y="470" />
<di:waypoint x="322" y="470" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1wxjn4e_di" bpmnElement="Flow_1wxjn4e">
<di:waypoint x="358" y="470" />
<di:waypoint x="432" y="470" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,46 @@
from datetime import timedelta
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine
from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition
from SpiffWorkflow.task import TaskState
from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase
class EventBsedGatewayTest(BpmnWorkflowTestCase):
def setUp(self):
self.spec, self.subprocesses = self.load_workflow_spec('event-gateway.bpmn', 'Process_0pvx19v')
self.script_engine = PythonScriptEngine(default_globals={"timedelta": timedelta})
self.workflow = BpmnWorkflow(self.spec, script_engine=self.script_engine)
def testEventBasedGateway(self):
self.actual_test()
def testEventBasedGatewaySaveRestore(self):
self.actual_test(True)
def actual_test(self, save_restore=False):
self.workflow.do_engine_steps()
waiting_tasks = self.workflow.get_waiting_tasks()
if save_restore:
self.save_restore()
self.workflow.script_engine = self.script_engine
self.assertEqual(len(waiting_tasks), 1)
self.workflow.catch(MessageEventDefinition('message_1'))
self.workflow.refresh_waiting_tasks()
self.workflow.do_engine_steps()
self.assertEqual(self.workflow.is_completed(), True)
self.assertEqual(self.workflow.get_tasks_from_spec_name('message_1_event')[0].state, TaskState.COMPLETED)
self.assertEqual(self.workflow.get_tasks_from_spec_name('message_2_event')[0].state, TaskState.CANCELLED)
self.assertEqual(self.workflow.get_tasks_from_spec_name('timer_event')[0].state, TaskState.CANCELLED)
def testMultipleStart(self):
spec, subprocess = self.load_workflow_spec('multiple-start-parallel.bpmn', 'main')
workflow = BpmnWorkflow(spec)
workflow.do_engine_steps()
workflow.catch(MessageEventDefinition('message_1'))
workflow.catch(MessageEventDefinition('message_2'))
workflow.refresh_waiting_tasks()
workflow.do_engine_steps()

View File

@ -0,0 +1,81 @@
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition
from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase
class MultipleStartEventTest(BpmnWorkflowTestCase):
def setUp(self):
self.spec, self.subprocesses = self.load_workflow_spec('multiple-start.bpmn', 'main')
self.workflow = BpmnWorkflow(self.spec)
def testMultipleStartEvent(self):
self.actual_test()
def testMultipleStartEventSaveRestore(self):
self.actual_test(True)
def actual_test(self, save_restore=False):
self.workflow.do_engine_steps()
waiting_tasks = self.workflow.get_waiting_tasks()
if save_restore:
self.save_restore()
# The start event should be waiting
self.assertEqual(len(waiting_tasks), 1)
self.assertEqual(waiting_tasks[0].task_spec.name, 'StartEvent_1')
self.workflow.catch(MessageEventDefinition('message_1'))
self.workflow.refresh_waiting_tasks()
self.workflow.do_engine_steps()
# Now the first task should be ready
ready_tasks = self.workflow.get_ready_user_tasks()
self.assertEqual(len(ready_tasks), 1)
self.assertEqual(ready_tasks[0].task_spec.name, 'any_task')
class ParallelStartEventTest(BpmnWorkflowTestCase):
def setUp(self):
self.spec, self.subprocesses = self.load_workflow_spec('multiple-start-parallel.bpmn', 'main')
self.workflow = BpmnWorkflow(self.spec)
def testParallelStartEvent(self):
self.actual_test()
def testParallelStartEventSaveRestore(self):
self.actual_test(True)
def actual_test(self, save_restore=False):
self.workflow.do_engine_steps()
waiting_tasks = self.workflow.get_waiting_tasks()
if save_restore:
self.save_restore()
# The start event should be waiting
self.assertEqual(len(waiting_tasks), 1)
self.assertEqual(waiting_tasks[0].task_spec.name, 'StartEvent_1')
self.workflow.catch(MessageEventDefinition('message_1'))
self.workflow.refresh_waiting_tasks()
self.workflow.do_engine_steps()
# It should still be waiting because it has to receive both messages
waiting_tasks = self.workflow.get_waiting_tasks()
self.assertEqual(len(waiting_tasks), 1)
self.assertEqual(waiting_tasks[0].task_spec.name, 'StartEvent_1')
self.workflow.catch(MessageEventDefinition('message_2'))
self.workflow.refresh_waiting_tasks()
self.workflow.do_engine_steps()
# Now the first task should be ready
ready_tasks = self.workflow.get_ready_user_tasks()
self.assertEqual(len(ready_tasks), 1)
self.assertEqual(ready_tasks[0].task_spec.name, 'any_task')

View File

@ -0,0 +1,47 @@
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase
class MultipleThrowEventIntermediateCatchTest(BpmnWorkflowTestCase):
def setUp(self):
self.spec, subprocesses = self.load_collaboration('multiple-throw.bpmn','top')
self.workflow = BpmnWorkflow(self.spec, subprocesses)
def testMultipleThrowEventIntermediateCatch(self):
self.actual_test()
def testMultipleThrowEventIntermediateCatchSaveRestore(self):
self.actual_test(True)
def actual_test(self, save_restore=False):
if save_restore:
self.save_restore()
self.workflow.do_engine_steps()
self.assertEqual(len(self.workflow.get_waiting_tasks()), 0)
self.assertEqual(self.workflow.is_completed(), True)
class MultipleThrowEventStartsEventTest(BpmnWorkflowTestCase):
def setUp(self):
specs = self.get_all_specs('multiple-throw-start.bpmn')
self.spec = specs.pop('initiate')
self.workflow = BpmnWorkflow(self.spec, specs)
def testMultipleThrowEventStartEvent(self):
self.actual_test()
def testMultipleThrowEventStartEventSaveRestore(self):
self.actual_test(True)
def actual_test(self, save_restore=False):
if save_restore:
self.save_restore()
self.workflow.do_engine_steps()
ready_tasks = self.workflow.get_ready_user_tasks()
self.assertEqual(len(ready_tasks), 1)
ready_tasks[0].complete()
self.workflow.do_engine_steps()
self.assertEqual(self.workflow.is_completed(), True)

View File

@ -3,6 +3,8 @@ import unittest
from SpiffWorkflow.dmn.engine.DMNEngine import DMNEngine
from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser
from SpiffWorkflow.dmn.serializer.task_spec_converters import \
BusinessRuleTaskConverter
from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase
from tests.SpiffWorkflow.dmn.DecisionRunner import DecisionRunner
from tests.SpiffWorkflow.dmn.python_engine.PythonDecisionRunner import \
@ -27,7 +29,18 @@ class HitPolicyTest(BpmnWorkflowTestCase):
self.assertEqual('COLLECT', decision_table.hit_policy)
res = runner.result({'type': 'stooge'})
self.assertEqual(4, len(res['name']))
res = runner.result({'type': 'farmer'})
self.assertEqual(1, len(res['name']))
self.assertEqual('Elmer Fudd', res['name'][0])
def testSerializeHitPolicy(self):
file_name = os.path.join(os.path.dirname(__file__), 'data', 'collect_hit.dmn')
runner = PythonDecisionRunner(file_name)
decision_table = runner.decision_table
self.assertEqual("COLLECT", decision_table.hit_policy)
dict = BusinessRuleTaskConverter().decision_table_to_dict(decision_table)
new_table = BusinessRuleTaskConverter().decision_table_from_dict(dict)
self.assertEqual("COLLECT", new_table.hit_policy)
def suite():
return unittest.TestLoader().loadTestsFromTestCase(HitPolicyTest)

View File

@ -3,9 +3,7 @@
import os
import sys
import unittest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
from tests.SpiffWorkflow.util import run_workflow
from .TaskSpecTest import TaskSpecTest
@ -25,7 +23,8 @@ class ExecuteTest(TaskSpecTest):
args=self.cmd_args)
def setUp(self):
self.cmd_args = ["python", "ExecuteProcessMock.py"]
script_path = os.path.join(os.path.dirname(__file__), '..', 'ExecuteProcessMock.py')
self.cmd_args = ["python", script_path]
TaskSpecTest.setUp(self)
def testConstructor(self):