spiff-arena/SpiffWorkflow/bpmn/specs/BpmnProcessSpec.py
Jon Herron 7c219fd731 Squashed 'SpiffWorkflow/' content from commit 63db3e4
git-subtree-dir: SpiffWorkflow
git-subtree-split: 63db3e45947ec66b8d0efc2c74064004f8ff482c
2022-10-12 10:19:53 -04:00

186 lines
6.1 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (C) 2012 Matthew Hampton
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
from copy import deepcopy
import logging
from SpiffWorkflow.bpmn.exceptions import WorkflowDataException
from ...task import TaskState
from .UnstructuredJoin import UnstructuredJoin
from ...specs.Simple import Simple
from ...specs.WorkflowSpec import WorkflowSpec
data_log = logging.getLogger('spiff.data')
class _EndJoin(UnstructuredJoin):
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(TaskState.READY | TaskState.WAITING):
if task.thread_id != my_task.thread_id:
continue
if task.task_spec == my_task.task_spec:
continue
is_mine = False
w = task.workflow
if w == my_task.workflow:
is_mine = True
while w and w.outer_workflow != w:
w = w.outer_workflow
if w == my_task.workflow:
is_mine = True
if is_mine:
waiting_tasks.append(task)
return force or len(waiting_tasks) == 0, waiting_tasks
def _on_complete_hook(self, my_task):
super(_EndJoin, self)._on_complete_hook(my_task)
my_task.workflow.data.update(my_task.data)
def serialize(self, serializer):
return serializer.serialize_join(self)
@classmethod
def deserialize(self, serializer, wf_spec, s_state):
return serializer.deserialize_join(wf_spec, s_state, _EndJoin)
class BpmnDataSpecification:
def __init__(self, name, description=None):
"""
:param name: the name of the task (the BPMN ID)
:param description: the task description (the BPMN name)
"""
self.name = name
self.description = description or name
# In the future, we can add schemas defining the objects here.
def get(self, my_task):
"""Copy a value form the workflow data to the task data."""
if self.name not in my_task.workflow.data:
message = f"Workflow variable {self.name} not found"
raise WorkflowDataException(my_task, data_input=self, message=message)
my_task.data[self.name] = deepcopy(my_task.workflow.data[self.name])
def set(self, my_task):
"""Copy a value from the task data to the workflow data"""
if self.name not in my_task.data:
message = f"Task variable {self.name} not found"
raise WorkflowDataException(my_task, data_output=self, message=message)
my_task.workflow.data[self.name] = deepcopy(my_task.data[self.name])
del my_task.data[self.name]
data_log.info(f'Set workflow variable {self.name}', extra=my_task.log_info())
def copy(self, source, destination, data_input=False, data_output=False):
"""Copy a value from one task to another."""
if self.name not in source.data:
message = f"Unable to copy {self.name}"
raise WorkflowDataException(
source,
data_input=self if data_input else None,
data_output=self if data_output else None,
message=message
)
destination.data[self.name] = deepcopy(source.data[self.name])
class BpmnProcessSpec(WorkflowSpec):
"""
This class represents the specification of a BPMN process workflow. This
specialises the standard Spiff WorkflowSpec class with a few extra methods
and attributes.
"""
def __init__(self, name=None, description=None, filename=None, svg=None):
"""
Constructor.
:param svg: This provides the SVG representation of the workflow as an
LXML node. (optional)
"""
super(BpmnProcessSpec, self).__init__(name=name, filename=filename)
self.end = _EndJoin(self, '%s.EndJoin' % (self.name))
end = Simple(self, 'End')
end.follow(self.end)
self.svg = svg
self.description = description
self.data_inputs = []
self.data_outputs = []
self.data_objects = {}
self.correlation_keys = {}
def get_all_lanes(self):
"""
Returns a set of the distinct lane names used in the process (including
called activities)
"""
done = set()
lanes = set()
def recursive_find(task_spec):
if task_spec in done:
return
done.add(task_spec)
if hasattr(task_spec, 'lane') and task_spec.lane:
lanes.add(task_spec.lane)
if hasattr(task_spec, 'spec'):
recursive_find(task_spec.spec.start)
for t in task_spec.outputs:
recursive_find(t)
recursive_find(self.start)
return lanes
def get_specs_depth_first(self):
"""
Get the specs for all processes (including called ones), in depth first
order.
"""
done = set()
specs = [self]
def recursive_find(task_spec):
if task_spec in done:
return
done.add(task_spec)
if hasattr(task_spec, 'spec'):
specs.append(task_spec.spec)
recursive_find(task_spec.spec.start)
for t in task_spec.outputs:
recursive_find(t)
recursive_find(self.start)
return specs