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:
parent
cc1903387b
commit
71a2a3ec0e
12
Makefile
12
Makefile
|
@ -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:
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
]
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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()
|
|
@ -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')
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue