Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions SpiffWorkflow/bpmn/parser/event_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,3 @@ def create_task(self):
def handles_multiple_outgoing(self):
return True

def connect_outgoing(self, outgoing_task, sequence_flow_node, is_default):
self.task.event_definition.event_definitions.append(outgoing_task.event_definition)
self.task.connect(outgoing_task)
18 changes: 18 additions & 0 deletions SpiffWorkflow/bpmn/serializer/migration/version_1_5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

def update_event_gateway_children(dct):

def update(tasks, specs):
for task in tasks:
task_spec = specs.get(task['task_spec'], {})
if task_spec['typename'] == 'EventBasedGateway':
for name in task_spec['outputs']:
child = specs.get(name)
child['event_definition'] = {
"description": "Default",
"name": None,
"typename": "NoneEventDefinition"
}

for up in dct['subprocesses'].values():
update(sp['tasks'].values(), sp['spec']['task_specs'])
update(dct['tasks'].values(), dct['spec']['task_specs'])
14 changes: 14 additions & 0 deletions SpiffWorkflow/bpmn/serializer/migration/version_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@
update_data_objects,
)
from .version_1_4 import update_mi_states
from .version_1_5 import update_event_gateway_children

def from_version_1_4(dct):
"""Upgrade serialization from v1.4 to v1.5

Event based gateways now manage events for their children. Duplicating the events in
multiple task specs was very difficult to manage and introduced a lot of problems with
task states and predictions. The gateway will handle dropping the branches for the
alternate events and the child branch with the matched event will proceed with a none
event.
"""
dct['VERSION'] = "1.5"
update_event_gateway_children(dct)

def from_version_1_3(dct):
"""Upgrade serialization from v1.3 to v1.4
Expand Down Expand Up @@ -122,4 +135,5 @@ def from_version_1_0(dct):
'1.1': from_version_1_1,
'1.2': from_version_1_2,
'1.3': from_version_1_3,
'1.4': from_version_1_4,
}
4 changes: 2 additions & 2 deletions SpiffWorkflow/bpmn/serializer/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from .config import DEFAULT_CONFIG

# This is the default version set on the workflow, it can be overridden in init
VERSION = "1.4"
VERSION = "1.5"


class BpmnWorkflowSerializer:
Expand Down Expand Up @@ -181,4 +181,4 @@ def from_dict(self, dct, **kwargs):
Returns:
a restored object
"""
return self.registry.restore(dct, **kwargs)
return self.registry.restore(dct, **kwargs)
32 changes: 20 additions & 12 deletions SpiffWorkflow/bpmn/specs/event_definitions/multiple.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from SpiffWorkflow.bpmn.util.event import BpmnEvent
from .timer import TimerEventDefinition, EventDefinition

class MultipleEventDefinition(EventDefinition):
Expand All @@ -11,27 +12,34 @@ def has_fired(self, my_task):

event_definitions = list(self.event_definitions)
seen_events = my_task.internal_data.get('seen_events', [])
for event_definition in self.event_definitions:
for event in seen_events:
if event.event_definition in event_definitions:
event_definitions.remove(event.event_definition)

for event_definition in event_definitions:
if isinstance(event_definition, TimerEventDefinition):
child = [c for c in my_task.children if c.task_spec.event_definition == event_definition]
child[0].task_spec._update_hook(child[0])
if event_definition.has_fired(child[0]) and event_definition in event_definitions:
if event_definition.has_fired(my_task):
event_definitions.remove(event_definition)
else:
for event in seen_events:
if event_definition.catches(my_task, event) and event_definition in event_definitions:
event_definitions.remove(event_definition)
seen_events.append(BpmnEvent(event_definition))

my_task.internal_data['seen_events'] = seen_events

if self.parallel:
# Parallel multiple need to match all events
return len(event_definitions) == 0
else:
return len(seen_events) > 0

def catches(self, my_task, event=None):
for item in self.event_definitions:
if item.catches(my_task, event):
return True

def catch(self, my_task, event=None):
event.event_definition.catch(my_task, event)
seen_events = my_task.internal_data.get('seen_events', []) + [event]
my_task._set_internal_data(seen_events=seen_events)
for item in self.event_definitions:
if item.catches(my_task, event):
seen_events = my_task.internal_data.get('seen_events', []) + [event]
my_task._set_internal_data(seen_events=seen_events)

def reset(self, my_task):
my_task.internal_data.pop('seen_events', None)
Expand All @@ -47,4 +55,4 @@ def __eq__(self, other):
def throw(self, my_task):
# Mutiple events throw all associated events when they fire
for event_definition in self.event_definitions:
event_definition.throw(my_task)
event_definition.throw(my_task)
24 changes: 19 additions & 5 deletions SpiffWorkflow/bpmn/specs/mixins/events/intermediate_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# 02110-1301 USA

from SpiffWorkflow.util.task import TaskState
from SpiffWorkflow.bpmn.specs.event_definitions import NoneEventDefinition
from .event_types import ThrowingEvent, CatchingEvent


Expand Down Expand Up @@ -53,11 +54,24 @@ def catches(self, my_task, event):

class EventBasedGateway(CatchingEvent):

def connect(self, child):
# Having the events duplicated in the gateway and the child is difficult to manage
# Therefore, I am going to have the gateway manage the events and remove the event from the child
super().connect(child)
self.event_definition.event_definitions.append(child.event_definition)
child.event_definition = NoneEventDefinition()

def _predict_hook(self, my_task):
my_task._sync_children(self.outputs, state=TaskState.WAITING)
my_task._sync_children(self.outputs, state=TaskState.MAYBE)

def _on_ready_hook(self, my_task):
for child in my_task.children:
if not child.internal_data.get('event_fired'):
child.cancel()
def _run_hook(self, my_task):
seen_events = my_task.internal_data.get('seen_events', [])
matches = []
for event in seen_events:
idx = self.event_definition.event_definitions.index(event.event_definition)
matches.append(my_task.children[idx].task_spec)

my_task._sync_children(matches, TaskState.FUTURE)
for child in my_task.children:
child.task_spec._predict(child, mask=TaskState.FUTURE|TaskState.PREDICTED_MASK)
return True
3 changes: 2 additions & 1 deletion SpiffWorkflow/bpmn/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from SpiffWorkflow.bpmn.specs.control import BoundaryEventSplit
from SpiffWorkflow.bpmn.specs.event_definitions.timer import TimerEventDefinition
from SpiffWorkflow.bpmn.specs.event_definitions.multiple import MultipleEventDefinition

from SpiffWorkflow.bpmn.util.subworkflow import BpmnBaseWorkflow, BpmnSubWorkflow
from SpiffWorkflow.bpmn.util.event import EventManager
Expand Down Expand Up @@ -181,7 +182,7 @@ def refresh_timers(self):
# Ideally this would go in event manager but I can't import the necessary classes there
# Eventually I'll move it
for task in list(self.event_manager.tasks.values()):
if isinstance(task.task_spec.event_definition, (TimerEventDefinition, )):
if isinstance(task.task_spec.event_definition, (TimerEventDefinition, MultipleEventDefinition)):
task.task_spec._update(task)

def get_task_from_id(self, task_id):
Expand Down
99 changes: 54 additions & 45 deletions tests/SpiffWorkflow/bpmn/data/event-gateway.bpmn
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
<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:sequenceFlow id="Flow_0w4b5t2" sourceRef="StartEvent_1" targetRef="Gateway_1dxbnbw" />
<bpmn:eventBasedGateway id="Gateway_1434v9l">
<bpmn:incoming>Flow_0w4b5t2</bpmn:incoming>
<bpmn:incoming>Flow_04a2n0x</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:outgoing>Flow_1xrb5hw</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_158nhox" messageRef="Message_0lyfmat" />
</bpmn:intermediateCatchEvent>
<bpmn:sequenceFlow id="Flow_0gge7fn" sourceRef="Gateway_1434v9l" targetRef="message_1_event" />
Expand All @@ -27,59 +27,32 @@
<bpmn:incoming>Flow_1rfbrlf</bpmn:incoming>
<bpmn:outgoing>Flow_0mppjk9</bpmn:outgoing>
<bpmn:timerEventDefinition id="TimerEventDefinition_0reo0gl">
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">"PT1S"</bpmn:timeDuration>
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">"PT0.1S"</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:intermediateCatchEvent>
<bpmn:sequenceFlow id="Flow_1rfbrlf" sourceRef="Gateway_1434v9l" targetRef="timer_event" />
<bpmn:endEvent id="timer">
<bpmn:endEvent id="timer_end">
<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:sequenceFlow id="Flow_0mppjk9" sourceRef="timer_event" targetRef="timer_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:exclusiveGateway id="Gateway_1dxbnbw" default="Flow_04a2n0x">
<bpmn:incoming>Flow_0w4b5t2</bpmn:incoming>
<bpmn:incoming>Flow_1xrb5hw</bpmn:incoming>
<bpmn:outgoing>Flow_04a2n0x</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:sequenceFlow id="Flow_04a2n0x" sourceRef="Gateway_1dxbnbw" targetRef="Gateway_1434v9l" />
<bpmn:sequenceFlow id="Flow_1xrb5hw" sourceRef="message_1_event" targetRef="Gateway_1dxbnbw" />
</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" />
<dc:Bounds x="52" 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" />
Expand All @@ -93,15 +66,51 @@
<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">
<bpmndi:BPMNShape id="Event_0emrepu_di" bpmnElement="timer_end">
<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:BPMNShape id="Gateway_1dxbnbw_di" bpmnElement="Gateway_1dxbnbw" isMarkerVisible="true">
<dc:Bounds x="155" y="92" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0w4b5t2_di" bpmnElement="Flow_0w4b5t2">
<di:waypoint x="88" y="117" />
<di:waypoint x="155" y="117" />
</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_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_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_0mppjk9_di" bpmnElement="Flow_0mppjk9">
<di:waypoint x="408" y="340" />
<di:waypoint x="472" y="340" />
</bpmndi:BPMNEdge>
<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_04a2n0x_di" bpmnElement="Flow_04a2n0x">
<di:waypoint x="205" y="117" />
<di:waypoint x="265" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1xrb5hw_di" bpmnElement="Flow_1xrb5hw">
<di:waypoint x="390" y="99" />
<di:waypoint x="390" y="0" />
<di:waypoint x="180" y="0" />
<di:waypoint x="180" y="92" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
Loading
Loading