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
12 changes: 11 additions & 1 deletion SpiffWorkflow/bpmn/parser/BpmnParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
CallActivity,
SubWorkflowTask,
TransactionSubprocess,
AdHocSubprocess,
InclusiveGateway,
ExclusiveGateway,
ParallelGateway,
Expand All @@ -49,7 +50,7 @@
from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import SubWorkflowTask as SubWorkflowTaskMixin

from .ValidationException import ValidationException
from .ProcessParser import ProcessParser
from .ProcessParser import ProcessParser, AdHocParser
from .node_parser import DEFAULT_NSMAP
from .spec_description import SPEC_DESCRIPTIONS
from .util import full_tag, xpath_eval, first
Expand All @@ -60,6 +61,7 @@
CallActivityParser,
ScriptTaskParser,
SubWorkflowParser,
AdHocSubprocessParser,
)
from .event_parsers import (
EventBasedGatewayParser,
Expand Down Expand Up @@ -132,6 +134,7 @@ class BpmnParser:
full_tag('inclusiveGateway'): (ConditionalGatewayParser, InclusiveGateway),
full_tag('callActivity'): (CallActivityParser, CallActivity),
full_tag('transaction'): (SubWorkflowParser, TransactionSubprocess),
full_tag('adHocSubProcess'): (AdHocSubprocessParser, AdHocSubprocess),
full_tag('scriptTask'): (ScriptTaskParser, ScriptTask),
full_tag('serviceTask'): (TaskParser, ServiceTask),
full_tag('intermediateCatchEvent'): (IntermediateCatchEventParser, IntermediateCatchEvent),
Expand All @@ -145,6 +148,7 @@ class BpmnParser:
OVERRIDE_PARSER_CLASSES = {}

PROCESS_PARSER_CLASS = ProcessParser
AD_HOC_PARSER_CLASS = AdHocParser

DATA_STORE_CLASSES = {}

Expand Down Expand Up @@ -402,6 +406,12 @@ def create_parser(self, node, filename=None, lane=None):
raise ValidationException(f'Duplicate process ID: {parser.bpmn_id}', node=node, file_name=filename)
self.process_parsers[parser.bpmn_id] = parser

def create_ad_hoc_parser(self, node, filename=None, lane=None):
parser = self.AD_HOC_PARSER_CLASS(self, node, self.namespaces, self.data_stores, filename=filename, lane=lane)
if parser.bpmn_id in self.process_parsers:
raise ValidationException(f'Duplicate process ID: {parser.bpmn_id}', node=node, file_name=filename)
self.process_parsers[parser.bpmn_id] = parser

def get_process_dependencies(self):
return self.process_dependencies

Expand Down
70 changes: 58 additions & 12 deletions SpiffWorkflow/bpmn/parser/ProcessParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
# 02110-1301 USA

from .ValidationException import ValidationException
from ..specs.bpmn_process_spec import BpmnProcessSpec
from ..specs.bpmn_process_spec import BpmnProcessSpec, AdHocSubprocessSpec
from ..specs.data_spec import DataObject
from ..specs.control import StartEventJoin, StartEventSplit
from .node_parser import NodeParser
from .util import first
from .util import first, full_tag


class ProcessParser(NodeParser):
Expand Down Expand Up @@ -156,14 +156,8 @@ def parse_node(self, node):
return task_spec

def _parse(self):
# here we only look in the top level, We will have another
# bpmn:startEvent if we have a subworkflow task
start_node_list = self.xpath('./bpmn:startEvent')
if not start_node_list and self.process_executable:
raise ValidationException("No start event found", node=self.node, file_name=self.filename)
if not self.process_executable:
raise ValidationException(f"Process {self.bpmn_id} is not executable.", node=self.node, file_name=self.filename)
self.spec = BpmnProcessSpec(name=self.bpmn_id, description=self.get_name(), filename=self.filename)

self.spec = self.create_spec()

# Get the data objects
for obj in self.xpath('./bpmn:dataObject'):
Expand All @@ -175,9 +169,20 @@ def _parse(self):
if io_spec is not None:
self.spec.io_specification = self.parse_io_spec()

# set the data stores on the process spec so they can survive
# serialization
self.spec.data_stores = self.data_stores
self.add_tasks()

def create_spec(self):
if not self.process_executable:
raise ValidationException(f"Process {self.bpmn_id} is not executable.", node=self.node, file_name=self.filename)
return BpmnProcessSpec(name=self.bpmn_id, description=self.get_name(), filename=self.filename)

def add_tasks(self):

start_node_list = self.xpath('./bpmn:startEvent')
if not start_node_list and self.process_executable:
raise ValidationException("No start event found", node=self.node, file_name=self.filename)

for node in start_node_list:
self.parse_node(node)

Expand Down Expand Up @@ -207,3 +212,44 @@ def get_spec(self):
if self.spec is None:
self._parse()
return self.spec

class AdHocParser(ProcessParser):

def _parse(self):
super()._parse()
self.spec.create_paths()

def create_spec(self):
if self.attribute('ordering') == 'sequential':
raise ValidationException(
'Sequential ordering for ad hoc subprocesses not supported',
node=self.node,
file_name=self.filename
)
cancel_remaining = self.attribute('cancelRemainingInstances') in [None, 'true']
condition = self.xpath('./bpmn:completionCondition')
condition = condition[0].text if len(condition) > 0 else None
return AdHocSubprocessSpec(
completion_condition=condition,
cancel_remaining=cancel_remaining,
name=self.bpmn_id,
description=self.get_name(),
filename=self.filename,
)

def add_tasks(self):
start_node_list = []
for node in self.node.getchildren():
if node.tag not in self.parser.PARSER_CLASSES:
continue
elif node.tag in [full_tag('startEvent'), full_tag('endEvent')]:
raise ValidationException(
'Ad hoc subprocesses may not contain start or end events',
node=self.node,
file_name=self.filename,
)
elif len(node.xpath('./bpmn:incoming', namespaces=self.nsmap)) == 0:
start_node_list.append(node)

for node in start_node_list:
self.parse_node(node)
20 changes: 20 additions & 0 deletions SpiffWorkflow/bpmn/parser/task_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ def get_call_activity_spec(task_parser):
file_name=task_parser.filename)
return called_element

@staticmethod
def get_ad_hoc_spec(task_parser):
task_parser.process_parser.parser.create_ad_hoc_parser(
task_parser.node,
filename=task_parser.filename,
lane=task_parser.lane
)
spec_id = task_parser.node.get('id')
# This parser makes me want to cry
spec_parser = task_parser.process_parser.parser.process_parsers[spec_id]
spec_parser.parent = task_parser.process_parser
return spec_id


class SubWorkflowParser(TaskParser):

Expand All @@ -109,6 +122,13 @@ def create_task(self):
return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes)


class AdHocSubprocessParser(TaskParser):

def create_task(self):
subworkflow_spec = SubprocessParser.get_ad_hoc_spec(self)
return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes)


class ScriptTaskParser(TaskParser):
"""Parses a script task"""

Expand Down
7 changes: 5 additions & 2 deletions SpiffWorkflow/bpmn/serializer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from SpiffWorkflow.bpmn import BpmnWorkflow, BpmnEvent
from SpiffWorkflow.bpmn.util.subworkflow import BpmnSubWorkflow

from SpiffWorkflow.bpmn.specs import BpmnProcessSpec
from SpiffWorkflow.bpmn.specs import BpmnProcessSpec, AdHocSubprocessSpec
from SpiffWorkflow.bpmn.specs.defaults import (
ManualTask,
NoneTask,
Expand All @@ -38,6 +38,7 @@
CallActivity,
TransactionSubprocess,
EventSubprocess,
AdHocSubprocess,
StartEvent,
EndEvent,
IntermediateCatchEvent,
Expand Down Expand Up @@ -83,7 +84,7 @@
BpmnEventConverter,
)
from .helpers import BpmnDataSpecificationConverter, EventDefinitionConverter
from .default import BpmnProcessSpecConverter
from .default import BpmnProcessSpecConverter, AdHocSubprocessSpecConverter
from .default.task_spec import (
BpmnTaskSpecConverter,
ScriptTaskConverter,
Expand Down Expand Up @@ -116,6 +117,7 @@
TaskDataReference: BpmnDataSpecificationConverter,
BpmnIoSpecification: IOSpecificationConverter,
BpmnProcessSpec: BpmnProcessSpecConverter,
AdHocSubprocessSpec: AdHocSubprocessSpecConverter,
SimpleBpmnTask: BpmnTaskSpecConverter,
BpmnStartTask: BpmnStartTaskConverter,
_EndJoin: BpmnTaskSpecConverter,
Expand All @@ -130,6 +132,7 @@
CallActivity: SubWorkflowConverter,
TransactionSubprocess: SubWorkflowConverter,
EventSubprocess: SubWorkflowConverter,
AdHocSubprocess: SubWorkflowConverter,
BoundaryEventSplit: BpmnTaskSpecConverter,
BoundaryEventJoin: EventJoinConverter,
ExclusiveGateway: ExclusiveGatewayConverter,
Expand Down
4 changes: 2 additions & 2 deletions SpiffWorkflow/bpmn/serializer/default/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# 02110-1301 USA

from .workflow import BpmnWorkflowConverter, BpmnSubWorkflowConverter, BpmnEventConverter
from .process_spec import BpmnProcessSpecConverter
from .process_spec import BpmnProcessSpecConverter, AdHocSubprocessSpecConverter
from .task_spec import (
BpmnTaskSpecConverter,
ScriptTaskConverter,
Expand All @@ -36,4 +36,4 @@
ErrorEscalationEventDefinitionConverter,
MessageEventDefinitionConverter,
MultipleEventDefinitionConverter,
)
)
31 changes: 31 additions & 0 deletions SpiffWorkflow/bpmn/serializer/default/process_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,34 @@ def from_dict(self, dct):
spec.bpmn_start_events = [spec.task_specs.get(name) for name in dct.get('bpmn_start_events', [])]

return spec

class AdHocSubprocessSpecConverter(BpmnProcessSpecConverter):

def to_dict(self, spec):
dct = super().to_dict(spec)
dct['completion_condition'] = spec.completion_condition
dct['parallel'] = True
dct['cancel_remaining'] = True
dct['conditional_paths'] = [sp.name for sp in spec.conditional_paths]
return dct

def from_dict(self, dct):

completion_condition = dct.pop('completion_condition')
parallel = dct.pop('parallel')
cancel_remaining = dct.pop('cancel_remaining')
conditional = dct.pop('conditional_paths')

spec = super().from_dict(dct)
spec.completion_condition = completion_condition
spec.parallel = parallel
spec.cancel_remaining = cancel_remaining

for name in conditional:
spec.conditional_paths.append(spec.task_specs.get(name))

for task_spec in spec.task_specs.values():
if spec.end.name in task_spec._outputs:
task_spec.completed_event.connect(spec.path_complete)

return spec
2 changes: 1 addition & 1 deletion SpiffWorkflow/bpmn/specs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@

from .bpmn_task_spec import BpmnTaskSpec
from .data_spec import BpmnDataSpecification, BpmnDataStoreSpecification
from .bpmn_process_spec import BpmnProcessSpec
from .bpmn_process_spec import BpmnProcessSpec, AdHocSubprocessSpec
Loading
Loading