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
11 changes: 11 additions & 0 deletions SpiffWorkflow/bpmn/parser/ProcessParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .ValidationException import ValidationException
from ..specs.bpmn_process_spec import BpmnProcessSpec
from ..specs.data_spec import DataObject
from ..specs.control import StartEventJoin, StartEventSplit
from .node_parser import NodeParser
from .util import first

Expand Down Expand Up @@ -136,6 +137,16 @@ def _parse(self):
for node in start_node_list:
self.parse_node(node)

if len(start_node_list) > 1:
split_task = StartEventSplit(self.spec, 'StartEventSplit')
join_task = StartEventJoin(self.spec, 'StartEventJoin', split_task='StartEventSplit', threshold=1, cancel=True)
for spec in self.spec.start.outputs:
spec.inputs = [split_task]
spec.connect(join_task)
split_task.outputs = self.spec.start.outputs
self.spec.start.outputs = [split_task]
split_task.inputs = [self.spec.start]

def parse_data_object(self, obj):
return DataObject(obj.get('id'), obj.get('name'))

Expand Down
8 changes: 6 additions & 2 deletions SpiffWorkflow/bpmn/serializer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
SimpleBpmnTask,
BoundaryEventSplit,
BoundaryEventJoin,
StartEventSplit,
StartEventJoin,
_EndJoin,
)
from SpiffWorkflow.bpmn.specs.data_spec import (
Expand All @@ -88,7 +90,7 @@
StandardLoopTaskConverter,
MultiInstanceTaskConverter,
SubWorkflowConverter,
BoundaryEventJoinConverter,
EventJoinConverter,
ConditionalGatewayConverter,
ExclusiveGatewayConverter,
ParallelGatewayConverter,
Expand Down Expand Up @@ -127,7 +129,7 @@
CallActivity: SubWorkflowConverter,
TransactionSubprocess: SubWorkflowConverter,
BoundaryEventSplit: BpmnTaskSpecConverter,
BoundaryEventJoin: BoundaryEventJoinConverter,
BoundaryEventJoin: EventJoinConverter,
ExclusiveGateway: ExclusiveGatewayConverter,
InclusiveGateway: ConditionalGatewayConverter,
ParallelGateway: ParallelGatewayConverter,
Expand All @@ -151,4 +153,6 @@
CycleTimerEventDefinition: TimerConditionalEventDefinitionConverter,
ConditionalEventDefinition: TimerConditionalEventDefinitionConverter,
MultipleEventDefinition: MultipleEventDefinitionConverter,
StartEventSplit: BpmnTaskSpecConverter,
StartEventJoin: EventJoinConverter,
}
4 changes: 2 additions & 2 deletions SpiffWorkflow/bpmn/serializer/default/task_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ def from_dict(self, dct):
return self.task_spec_from_dict(dct)


class BoundaryEventJoinConverter(BpmnTaskSpecConverter):
"""The default converter for `BoundaryEventJoin`"""
class EventJoinConverter(BpmnTaskSpecConverter):
"""The default converter for event join gateways"""

def to_dict(self, spec):
dct = super().to_dict(spec)
Expand Down
46 changes: 40 additions & 6 deletions SpiffWorkflow/bpmn/specs/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from SpiffWorkflow.bpmn.specs.bpmn_task_spec import BpmnTaskSpec
from SpiffWorkflow.bpmn.specs.mixins.unstructured_join import UnstructuredJoin
from SpiffWorkflow.bpmn.specs.mixins.events.intermediate_event import BoundaryEvent
from SpiffWorkflow.bpmn.specs.mixins.events.start_event import StartEvent


class BpmnStartTask(BpmnTaskSpec, StartTask):
Expand All @@ -33,24 +34,37 @@ class BpmnStartTask(BpmnTaskSpec, StartTask):
class SimpleBpmnTask(BpmnTaskSpec):
pass

class EventSplit(SimpleBpmnTask):

class BoundaryEventSplit(SimpleBpmnTask):
def __init__(self, event_type, wf_spec, name, **kwargs):
super().__init__(wf_spec, name, **kwargs)
self.event_type = event_type

def _predict_hook(self, my_task):
# Events attached to the main task might occur
my_task._sync_children(self.outputs, state=TaskState.MAYBE)
# The main child's state is based on this task's state
state = TaskState.FUTURE if my_task.has_state(TaskState.DEFINITE_MASK) else my_task.state
for child in my_task.children:
if not isinstance(child.task_spec, BoundaryEvent):
if not isinstance(child.task_spec, self.event_type):
child._set_state(state)

def _run_hook(self, my_task):
for task in my_task.children:
if isinstance(task.task_spec, BoundaryEvent) and task.has_state(TaskState.PREDICTED_MASK):
if isinstance(task.task_spec, self.event_type) and task.has_state(TaskState.PREDICTED_MASK):
task._set_state(TaskState.WAITING)
return True



class BoundaryEventSplit(EventSplit):
def __init__(self, wf_spec, name, **kwargs):
super().__init__(BoundaryEvent, wf_spec, name, **kwargs)


class StartEventSplit(EventSplit):
def __init__(self, wf_spec, name, **kwargs):
super().__init__(StartEvent, wf_spec, name, **kwargs)


class BoundaryEventJoin(Join, BpmnTaskSpec):
"""This task is inserted before a task with boundary events."""
Expand All @@ -59,7 +73,6 @@ def __init__(self, wf_spec, name, **kwargs):
super().__init__(wf_spec, name, **kwargs)

def _check_threshold_structured(self, my_task, force=False):
# Retrieve a list of all activated tasks from the associated task that did the conditional parallel split.
split_task = my_task.find_ancestor(self.split_task)
if split_task is None:
raise WorkflowException(f'Split at {self.split_task} was not reached', task_spec=self)
Expand Down Expand Up @@ -87,13 +100,34 @@ def _check_threshold_structured(self, my_task, force=False):
return force or finished, cancel


class StartEventJoin(Join, BpmnTaskSpec):

def __init__(self, wf_spec, name, **kwargs):
super().__init__(wf_spec, name, **kwargs)

def _check_threshold_structured(self, my_task, force=False):

split_task = my_task.find_ancestor(self.split_task)
if split_task is None:
raise WorkflowException(f'Split at {self.split_task} was not reached', task_spec=self)

may_fire, waiting = False, []
for task in split_task.children:
if task.state == TaskState.COMPLETED:
may_fire = True
else:
waiting.append(task)

return force or may_fire, waiting


class _EndJoin(UnstructuredJoin, BpmnTaskSpec):

def _check_threshold_unstructured(self, my_task, force=False):
# Look at the tree to find all ready and waiting tasks (excluding
# ourself). The EndJoin waits for everyone!
waiting_tasks = []
for task in my_task.workflow.get_tasks(task_filter=TaskFilter(state=TaskState.READY|TaskState.WAITING)):
for task in my_task.workflow.get_tasks(state=TaskState.READY|TaskState.WAITING):
if task.thread_id != my_task.thread_id:
continue
if task.task_spec == my_task.task_spec:
Expand Down
2 changes: 1 addition & 1 deletion SpiffWorkflow/bpmn/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def catch(self, event):
"""
if event.target is None:
self.update_collaboration(event)
tasks = self.get_tasks(catches_event=event)
tasks = self.get_tasks(state=TaskState.NOT_FINISHED_MASK, catches_event=event)
# Figure out if we need to create an external event
if len(tasks) == 0:
self.bpmn_events.append(event)
Expand Down
14 changes: 8 additions & 6 deletions tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,22 @@ def load_workflow_spec(self, filename, process_name, validate=True):
return top_level_spec, subprocesses

def load_collaboration(self, filename, collaboration_name):
f = os.path.join(os.path.dirname(__file__), 'data', filename)
parser = TestBpmnParser()
parser.add_bpmn_files_by_glob(f)
parser = self.get_parser(filename)
return parser.get_collaboration(collaboration_name)

def get_all_specs(self, filename):
f = os.path.join(os.path.dirname(__file__), 'data', filename)
parser = TestBpmnParser()
parser.add_bpmn_files_by_glob(f)
parser = self.get_parser(filename)
return parser.find_all_specs()

def get_ready_user_tasks(self, lane=None):
return self.workflow.get_tasks(state=TaskState.READY, manual=True, lane=lane)

def run_until_input_required(self):
task = self.workflow.get_next_task(state=TaskState.READY, manual=False)
while task is not None:
task.run()
task = self.workflow.get_next_task(state=TaskState.READY, manual=False)

def do_next_exclusive_step(self, step_name, with_save_load=False, set_attribs=None, choice=None):
if with_save_load:
self.save_restore_all()
Expand Down
68 changes: 68 additions & 0 deletions tests/SpiffWorkflow/bpmn/data/start_event_split.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?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:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0q4k183" 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="main" isExecutable="true">
<bpmn:startEvent id="start_1">
<bpmn:outgoing>Flow_0jd2wd1</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_10bhnu0" messageRef="Message_1honxk2" />
</bpmn:startEvent>
<bpmn:startEvent id="start_2">
<bpmn:outgoing>Flow_0gdww0i</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_1lmbfg0" messageRef="Message_1vtwc8d" />
</bpmn:startEvent>
<bpmn:exclusiveGateway id="Gateway_0wdqp5z" default="Flow_0chx8fe">
<bpmn:incoming>Flow_0jd2wd1</bpmn:incoming>
<bpmn:incoming>Flow_0gdww0i</bpmn:incoming>
<bpmn:outgoing>Flow_0chx8fe</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:sequenceFlow id="Flow_0jd2wd1" sourceRef="start_1" targetRef="Gateway_0wdqp5z" />
<bpmn:sequenceFlow id="Flow_0gdww0i" sourceRef="start_2" targetRef="Gateway_0wdqp5z" />
<bpmn:task id="any_task" name="Any Task">
<bpmn:incoming>Flow_0chx8fe</bpmn:incoming>
<bpmn:outgoing>Flow_10jmygo</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_0chx8fe" sourceRef="Gateway_0wdqp5z" targetRef="any_task" />
<bpmn:endEvent id="Event_1kljwfy">
<bpmn:incoming>Flow_10jmygo</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_10jmygo" sourceRef="any_task" targetRef="Event_1kljwfy" />
</bpmn:process>
<bpmn:message id="Message_1honxk2" name="message_1" />
<bpmn:message id="Message_1vtwc8d" name="message_2" />
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="main">
<bpmndi:BPMNEdge id="Flow_0jd2wd1_di" bpmnElement="Flow_0jd2wd1">
<di:waypoint x="215" y="97" />
<di:waypoint x="290" y="97" />
<di:waypoint x="290" y="125" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0gdww0i_di" bpmnElement="Flow_0gdww0i">
<di:waypoint x="215" y="210" />
<di:waypoint x="290" y="210" />
<di:waypoint x="290" y="175" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0chx8fe_di" bpmnElement="Flow_0chx8fe">
<di:waypoint x="315" y="150" />
<di:waypoint x="370" y="150" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_10jmygo_di" bpmnElement="Flow_10jmygo">
<di:waypoint x="470" y="150" />
<di:waypoint x="532" y="150" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Event_1bzgjxg_di" bpmnElement="start_1">
<dc:Bounds x="179" y="79" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_036b8fy_di" bpmnElement="start_2">
<dc:Bounds x="179" y="192" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_0wdqp5z_di" bpmnElement="Gateway_0wdqp5z" isMarkerVisible="true">
<dc:Bounds x="265" y="125" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0iwkyb9_di" bpmnElement="any_task">
<dc:Bounds x="370" y="110" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1kljwfy_di" bpmnElement="Event_1kljwfy">
<dc:Bounds x="532" y="132" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
54 changes: 26 additions & 28 deletions tests/SpiffWorkflow/bpmn/data/timer-cycle-start.bpmn
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,16 @@
<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" id="Definitions_0ilr8m3" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.11.1">
<bpmn:collaboration id="Collaboration_0bcl3k5">
<bpmn:participant id="Participant_10cebxh" processRef="timer" />
<bpmn:participant id="Participant_1uwlcqw" processRef="Process_0vaq70r" />
</bpmn:collaboration>
<bpmn:process id="timer" isExecutable="true">
<bpmn:laneSet id="LaneSet_19op0am">
<bpmn:lane id="Lane_13pry5y">
<bpmn:flowNodeRef>StartEvent</bpmn:flowNodeRef>
<bpmn:flowNodeRef>wait_timer</bpmn:flowNodeRef>
<bpmn:flowNodeRef>EndItAll</bpmn:flowNodeRef>
</bpmn:lane>
<bpmn:lane id="Lane_1qb9sc3">
<bpmn:flowNodeRef>CycleStart</bpmn:flowNodeRef>
<bpmn:flowNodeRef>Refill_Coffee</bpmn:flowNodeRef>
<bpmn:flowNodeRef>CycleEnd</bpmn:flowNodeRef>
</bpmn:lane>
</bpmn:laneSet>
<bpmn:startEvent id="StartEvent">
<bpmn:outgoing>Flow_1pahvlr</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:startEvent id="CycleStart">
<bpmn:outgoing>Flow_0jtfzsk</bpmn:outgoing>
<bpmn:timerEventDefinition id="TimerEventDefinition_0za5f2h">
Expand All @@ -33,6 +26,13 @@
<bpmn:endEvent id="CycleEnd">
<bpmn:incoming>Flow_07sglzn</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_07sglzn" sourceRef="Refill_Coffee" targetRef="CycleEnd" />
<bpmn:sequenceFlow id="Flow_0jtfzsk" sourceRef="CycleStart" targetRef="Refill_Coffee" />
</bpmn:process>
<bpmn:process id="Process_0vaq70r" isExecutable="true">
<bpmn:startEvent id="StartEvent">
<bpmn:outgoing>Flow_1pahvlr</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:intermediateCatchEvent id="wait_timer">
<bpmn:incoming>Flow_1pahvlr</bpmn:incoming>
<bpmn:outgoing>Flow_05ejbm4</bpmn:outgoing>
Expand All @@ -45,25 +45,16 @@
<bpmn:terminateEventDefinition id="TerminateEventDefinition_02us6b3" />
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1pahvlr" sourceRef="StartEvent" targetRef="wait_timer" />
<bpmn:sequenceFlow id="Flow_07sglzn" sourceRef="Refill_Coffee" targetRef="CycleEnd" />
<bpmn:sequenceFlow id="Flow_0jtfzsk" sourceRef="CycleStart" targetRef="Refill_Coffee" />
<bpmn:sequenceFlow id="Flow_05ejbm4" sourceRef="wait_timer" targetRef="EndItAll" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_0bcl3k5">
<bpmndi:BPMNShape id="Participant_10cebxh_di" bpmnElement="Participant_10cebxh" isHorizontal="true">
<dc:Bounds x="158" y="84" width="600" height="380" />
<dc:Bounds x="158" y="84" width="600" height="166" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Lane_1qb9sc3_di" bpmnElement="Lane_1qb9sc3" isHorizontal="true">
<dc:Bounds x="188" y="84" width="570" height="120" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Lane_13pry5y_di" bpmnElement="Lane_13pry5y" isHorizontal="true">
<dc:Bounds x="188" y="204" width="570" height="260" />
<dc:Bounds x="188" y="84" width="570" height="166" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_05ejbm4_di" bpmnElement="Flow_05ejbm4">
<di:waypoint x="388" y="267" />
<di:waypoint x="492" y="267" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0jtfzsk_di" bpmnElement="Flow_0jtfzsk">
<di:waypoint x="248" y="150" />
<di:waypoint x="300" y="150" />
Expand All @@ -72,13 +63,6 @@
<di:waypoint x="400" y="150" />
<di:waypoint x="452" y="150" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1pahvlr_di" bpmnElement="Flow_1pahvlr">
<di:waypoint x="258" y="267" />
<di:waypoint x="352" y="267" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent">
<dc:Bounds x="222" y="249" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_051yw6f_di" bpmnElement="CycleStart">
<dc:Bounds x="212" y="132" width="36" height="36" />
</bpmndi:BPMNShape>
Expand All @@ -88,11 +72,25 @@
<bpmndi:BPMNShape id="Event_19emh1x_di" bpmnElement="CycleEnd">
<dc:Bounds x="452" y="132" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_1uwlcqw_di" bpmnElement="Participant_1uwlcqw" isHorizontal="true">
<dc:Bounds x="158" y="290" width="600" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_05ejbm4_di" bpmnElement="Flow_05ejbm4">
<di:waypoint x="388" y="390" />
<di:waypoint x="492" y="390" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1pahvlr_di" bpmnElement="Flow_1pahvlr">
<di:waypoint x="258" y="390" />
<di:waypoint x="352" y="390" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent">
<dc:Bounds x="222" y="372" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_19crrpp_di" bpmnElement="wait_timer">
<dc:Bounds x="352" y="249" width="36" height="36" />
<dc:Bounds x="352" y="372" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_10njezb_di" bpmnElement="EndItAll">
<dc:Bounds x="492" y="249" width="36" height="36" />
<dc:Bounds x="492" y="372" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
Expand Down
Loading