diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/BpmnParser.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/BpmnParser.py index cd956f29..c6ed81bb 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/BpmnParser.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/BpmnParser.py @@ -18,8 +18,10 @@ # 02110-1301 USA import glob +import os from lxml import etree +from lxml.etree import DocumentInvalid from SpiffWorkflow.bpmn.specs.events.event_definitions import NoneEventDefinition @@ -37,6 +39,7 @@ from ..specs.ScriptTask import ScriptTask from ..specs.ServiceTask import ServiceTask from ..specs.UserTask import UserTask from .ProcessParser import ProcessParser +from .node_parser import DEFAULT_NSMAP from .util import full_tag, xpath_eval, first from .task_parsers import (UserTaskParser, NoneTaskParser, ManualTaskParser, ExclusiveGatewayParser, ParallelGatewayParser, InclusiveGatewayParser, @@ -47,6 +50,28 @@ from .event_parsers import (StartEventParser, EndEventParser, BoundaryEventParse SendTaskParser, ReceiveTaskParser) +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)) + if imports is not None: + for ns, fn in imports.items(): + elem = etree.Element( + '{http://www.w3.org/2001/XMLSchema}import', + namespace=ns, + schemaLocation=fn + ) + schema.getroot().insert(0, elem) + self.validator = etree.XMLSchema(schema) + + def validate(self, bpmn, filename=None): + try: + self.validator.assertValid(bpmn) + except DocumentInvalid as di: + raise DocumentInvalid(str(di) + "file: " + filename) + class BpmnParser(object): """ The BpmnParser class is a pluggable base class that manages the parsing of @@ -83,15 +108,16 @@ class BpmnParser(object): PROCESS_PARSER_CLASS = ProcessParser - def __init__(self): + def __init__(self, namespaces=None, validator=None): """ Constructor. """ + self.namespaces = namespaces or DEFAULT_NSMAP + self.validator = validator self.process_parsers = {} self.process_parsers_by_name = {} self.collaborations = {} self.process_dependencies = set() - self.dmn_dependencies = set() def _get_parser_class(self, tag): if tag in self.OVERRIDE_PARSER_CLASSES: @@ -146,46 +172,31 @@ class BpmnParser(object): file :param filename: Optionally, provide the source filename. """ - xpath = xpath_eval(bpmn) - # do a check on our bpmn to ensure that no id appears twice - # this *should* be taken care of by our modeler - so this test - # should never fail. - ids = [x for x in xpath('.//bpmn:*[@id]')] - foundids = {} - for node in ids: - id = node.get('id') - if foundids.get(id,None) is not None: - raise ValidationException( - 'The bpmn document should have no repeating ids but (%s) repeats'%id, - node=node, - filename=filename) - else: - foundids[id] = 1 + if self.validator: + self.validator.validate(bpmn, filename) - for process in xpath('.//bpmn:process'): - self.create_parser(process, xpath, filename) + self._add_processes(bpmn, filename) + self._add_collaborations(bpmn) - self._find_dependencies(xpath) + def _add_processes(self, bpmn, filename=None): + for process in bpmn.xpath('.//bpmn:process', namespaces=self.namespaces): + self._find_dependencies(process) + self.create_parser(process, filename) - collaboration = first(xpath('.//bpmn:collaboration')) + def _add_collaborations(self, bpmn): + collaboration = first(bpmn.xpath('.//bpmn:collaboration', namespaces=self.namespaces)) if collaboration is not None: collaboration_xpath = xpath_eval(collaboration) name = collaboration.get('id') self.collaborations[name] = [ participant.get('processRef') for participant in collaboration_xpath('.//bpmn:participant') ] - def _find_dependencies(self, xpath): - """Locate all calls to external BPMN and DMN files, and store their - ids in our list of dependencies""" - for call_activity in xpath('.//bpmn:callActivity'): + def _find_dependencies(self, process): + """Locate all calls to external BPMN, and store their ids in our list of dependencies""" + for call_activity in process.xpath('.//bpmn:callActivity', namespaces=self.namespaces): self.process_dependencies.add(call_activity.get('calledElement')) - parser_cls, cls = self._get_parser_class(full_tag('businessRuleTask')) - if parser_cls: - for business_rule in xpath('.//bpmn:businessRuleTask'): - self.dmn_dependencies.add(parser_cls.get_decision_ref(business_rule)) - - def create_parser(self, node, doc_xpath, filename=None, lane=None): - parser = self.PROCESS_PARSER_CLASS(self, node, filename=filename, doc_xpath=doc_xpath, lane=lane) + def create_parser(self, node, filename=None, lane=None): + parser = self.PROCESS_PARSER_CLASS(self, node, self.namespaces, filename=filename, lane=lane) if parser.get_id() in self.process_parsers: raise ValidationException('Duplicate process ID', node=node, filename=filename) if parser.get_name() in self.process_parsers_by_name: @@ -194,14 +205,11 @@ class BpmnParser(object): self.process_parsers_by_name[parser.get_name()] = parser def get_dependencies(self): - return self.process_dependencies.union(self.dmn_dependencies) + return self.process_dependencies def get_process_dependencies(self): return self.process_dependencies - def get_dmn_dependencies(self): - return self.dmn_dependencies - def get_spec(self, process_id_or_name): """ Parses the required subset of the BPMN files, in order to provide an diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/ProcessParser.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/ProcessParser.py index 90019fe8..6fd3a427 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/ProcessParser.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/ProcessParser.py @@ -29,7 +29,7 @@ class ProcessParser(NodeParser): process. """ - def __init__(self, p, node, filename=None, doc_xpath=None, lane=None): + def __init__(self, p, node, nsmap, filename=None, lane=None): """ Constructor. @@ -39,7 +39,7 @@ class ProcessParser(NodeParser): :param doc_xpath: an xpath evaluator for the document (optional) :param lane: the lane of a subprocess (optional) """ - super().__init__(node, filename, doc_xpath, lane) + super().__init__(node, nsmap, filename=filename, lane=lane) self.parser = p self.parsed_nodes = {} self.lane = lane @@ -58,7 +58,6 @@ class ProcessParser(NodeParser): can be called by a TaskParser instance, that is owned by this ProcessParser. """ - if node.get('id') in self.parsed_nodes: return self.parsed_nodes[node.get('id')] @@ -66,7 +65,7 @@ class ProcessParser(NodeParser): if not node_parser or not spec_class: raise ValidationException("There is no support implemented for this task type.", node=node, filename=self.filename) - np = node_parser(self, spec_class, node, self.lane) + np = node_parser(self, spec_class, node, lane=self.lane) task_spec = np.parse_node() return task_spec @@ -82,12 +81,12 @@ class ProcessParser(NodeParser): # Check for an IO Specification. io_spec = first(self.xpath('./bpmn:ioSpecification')) if io_spec is not None: - data_parser = DataSpecificationParser(io_spec, self.filename, self.doc_xpath) + data_parser = DataSpecificationParser(io_spec, filename=self.filename) self.spec.data_inputs, self.spec.data_outputs = data_parser.parse_io_spec() # Get the data objects for obj in self.xpath('./bpmn:dataObject'): - data_parser = DataSpecificationParser(obj, self.filename, self.doc_xpath) + data_parser = DataSpecificationParser(obj, filename=self.filename) data_object = data_parser.parse_data_object() self.spec.data_objects[data_object.name] = data_object diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/TaskParser.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/TaskParser.py index bbfbae45..afbd1972 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/TaskParser.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/TaskParser.py @@ -46,7 +46,7 @@ class TaskParser(NodeParser): outgoing transitions, once the child tasks have all been parsed. """ - def __init__(self, process_parser, spec_class, node, lane=None): + def __init__(self, process_parser, spec_class, node, nsmap=None, lane=None): """ Constructor. @@ -56,7 +56,7 @@ class TaskParser(NodeParser): extending the TaskParser. :param node: the XML node for this task """ - super().__init__(node, process_parser.filename, process_parser.doc_xpath, lane) + super().__init__(node, nsmap, filename=process_parser.filename, lane=lane) self.process_parser = process_parser self.spec_class = spec_class self.spec = self.process_parser.spec diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/node_parser.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/node_parser.py index f4d065a8..a6f05a57 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/node_parser.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/node_parser.py @@ -1,30 +1,41 @@ from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException -from .util import xpath_eval, first +from .util import first + +DEFAULT_NSMAP = { + 'bpmn': 'http://www.omg.org/spec/BPMN/20100524/MODEL', + 'bpmndi': 'http://www.omg.org/spec/BPMN/20100524/DI', + 'dc': 'http://www.omg.org/spec/DD/20100524/DC', + +} CAMUNDA_MODEL_NS = 'http://camunda.org/schema/1.0/bpmn' class NodeParser: - def __init__(self, node, filename, doc_xpath, lane=None): + def __init__(self, node, nsmap=None, filename=None, lane=None): self.node = node + self.nsmap = nsmap or DEFAULT_NSMAP self.filename = filename - self.doc_xpath = doc_xpath - self.xpath = xpath_eval(node) self.lane = self._get_lane() or lane self.position = self._get_position() or {'x': 0.0, 'y': 0.0} def get_id(self): return self.node.get('id') + + def xpath(self, xpath, extra_ns=None): + return self._xpath(self.node, xpath, extra_ns) + def doc_xpath(self, xpath, extra_ns=None): + root = self.node.getroottree().getroot() + return self._xpath(root, xpath, extra_ns) + def parse_condition(self, sequence_flow): - xpath = xpath_eval(sequence_flow) - expression = first(xpath('.//bpmn:conditionExpression')) + expression = first(self._xpath(sequence_flow, './/bpmn:conditionExpression')) return expression.text if expression is not None else None def parse_documentation(self, sequence_flow=None): - xpath = xpath_eval(sequence_flow) if sequence_flow is not None else self.xpath - documentation_node = first(xpath('.//bpmn:documentation')) + documentation_node = first(self._xpath(sequence_flow or self.node, './/bpmn:documentation')) return None if documentation_node is None else documentation_node.text def parse_incoming_data_references(self): @@ -50,8 +61,7 @@ class NodeParser: def parse_extensions(self, node=None): extensions = {} extra_ns = {'camunda': CAMUNDA_MODEL_NS} - xpath = xpath_eval(self.node, extra_ns) if node is None else xpath_eval(node, extra_ns) - extension_nodes = xpath( './/bpmn:extensionElements/camunda:properties/camunda:property') + extension_nodes = self.xpath('.//bpmn:extensionElements/camunda:properties/camunda:property', extra_ns) for node in extension_nodes: extensions[node.get('name')] = node.get('value') return extensions @@ -65,3 +75,11 @@ class NodeParser: bounds = first(self.doc_xpath(f".//bpmndi:BPMNShape[@bpmnElement='{self.get_id()}']//dc:Bounds")) if bounds is not None: return {'x': float(bounds.get('x', 0)), 'y': float(bounds.get('y', 0))} + + def _xpath(self, node, xpath, extra_ns=None): + if extra_ns is not None: + nsmap = self.nsmap.copy() + nsmap.update(extra_ns) + else: + nsmap = self.nsmap + return node.xpath(xpath, namespaces=nsmap) \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/BPMN20.xsd b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/BPMN20.xsd new file mode 100644 index 00000000..2385f8e9 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/BPMN20.xsd @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/BPMNDI.xsd b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/BPMNDI.xsd new file mode 100644 index 00000000..615740f4 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/BPMNDI.xsd @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/DC.xsd b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/DC.xsd new file mode 100644 index 00000000..8b622c33 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/DC.xsd @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/DI.xsd b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/DI.xsd new file mode 100644 index 00000000..4023d288 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/DI.xsd @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/Semantic.xsd b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/Semantic.xsd new file mode 100644 index 00000000..6d380b33 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/schema/Semantic.xsd @@ -0,0 +1,1559 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/task_parsers.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/task_parsers.py index ba477684..8b071a77 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/task_parsers.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/task_parsers.py @@ -138,7 +138,6 @@ class SubprocessParser: task_parser.process_parser.parser.create_parser( task_parser.node, - doc_xpath=task_parser.doc_xpath, filename=task_parser.filename, lane=task_parser.lane ) diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/parser/CamundaParser.py b/SpiffWorkflow/SpiffWorkflow/camunda/parser/CamundaParser.py index f8145ab1..b9d0dc26 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/parser/CamundaParser.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/parser/CamundaParser.py @@ -1,10 +1,10 @@ from ..specs.UserTask import UserTask -from ..parser.UserTaskParser import UserTaskParser +from ..parser.task_spec import UserTaskParser from ...bpmn.parser.BpmnParser import full_tag from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask -from SpiffWorkflow.camunda.parser.business_rule_task import BusinessRuleTaskParser +from SpiffWorkflow.camunda.parser.task_spec import BusinessRuleTaskParser from SpiffWorkflow.bpmn.specs.events import EndEvent, IntermediateThrowEvent, StartEvent, IntermediateCatchEvent, BoundaryEvent from .event_parsers import CamundaStartEventParser, CamundaEndEventParser, \ diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/parser/business_rule_task.py b/SpiffWorkflow/SpiffWorkflow/camunda/parser/business_rule_task.py deleted file mode 100644 index fc2059dc..00000000 --- a/SpiffWorkflow/SpiffWorkflow/camunda/parser/business_rule_task.py +++ /dev/null @@ -1,38 +0,0 @@ -from SpiffWorkflow.bpmn.parser.util import xpath_eval -from SpiffWorkflow.bpmn.parser.TaskParser import TaskParser - -from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask - -CAMUNDA_MODEL_NS = 'http://camunda.org/schema/1.0/bpmn' - - -class BusinessRuleTaskParser(TaskParser): - dmn_debug = None - - def __init__(self, process_parser, spec_class, node, lane=None): - super(BusinessRuleTaskParser, self).__init__(process_parser, spec_class, node, lane) - self.xpath = xpath_eval(self.node, extra_ns={'camunda': CAMUNDA_MODEL_NS}) - - def create_task(self): - decision_ref = self.get_decision_ref(self.node) - return BusinessRuleTask(self.spec, self.get_task_spec_name(), - dmnEngine=self.process_parser.parser.get_engine(decision_ref, self.node), - lane=self.lane, position=self.position, - description=self.node.get('name', None), - ) - - @staticmethod - def get_decision_ref(node): - return node.attrib['{' + CAMUNDA_MODEL_NS + '}decisionRef'] - - - def _on_trigger(self, my_task): - pass - - def serialize(self, serializer, **kwargs): - pass - - @classmethod - def deserialize(cls, serializer, wf_spec, s_state, **kwargs): - pass - diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/parser/UserTaskParser.py b/SpiffWorkflow/SpiffWorkflow/camunda/parser/task_spec.py similarity index 60% rename from SpiffWorkflow/SpiffWorkflow/camunda/parser/UserTaskParser.py rename to SpiffWorkflow/SpiffWorkflow/camunda/parser/task_spec.py index 9d4341aa..c62f464e 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/parser/UserTaskParser.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/parser/task_spec.py @@ -1,18 +1,54 @@ -from ...bpmn.parser.TaskParser import TaskParser -from ...bpmn.parser.util import xpath_eval from ...camunda.specs.UserTask import Form, FormField, EnumFormField +from SpiffWorkflow.bpmn.parser.TaskParser import TaskParser +from SpiffWorkflow.bpmn.parser.node_parser import DEFAULT_NSMAP + +from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask + CAMUNDA_MODEL_NS = 'http://camunda.org/schema/1.0/bpmn' + +class BusinessRuleTaskParser(TaskParser): + dmn_debug = None + + def __init__(self, process_parser, spec_class, node, lane=None): + nsmap = DEFAULT_NSMAP.copy() + nsmap.update({'camunda': CAMUNDA_MODEL_NS}) + super(BusinessRuleTaskParser, self).__init__(process_parser, spec_class, node, nsmap, lane) + + def create_task(self): + decision_ref = self.get_decision_ref(self.node) + return BusinessRuleTask(self.spec, self.get_task_spec_name(), + dmnEngine=self.process_parser.parser.get_engine(decision_ref, self.node), + lane=self.lane, position=self.position, + description=self.node.get('name', None), + ) + + @staticmethod + def get_decision_ref(node): + return node.attrib['{' + CAMUNDA_MODEL_NS + '}decisionRef'] + + def _on_trigger(self, my_task): + pass + + def serialize(self, serializer, **kwargs): + pass + + @classmethod + def deserialize(cls, serializer, wf_spec, s_state, **kwargs): + pass + + class UserTaskParser(TaskParser): """ Base class for parsing User Tasks """ def __init__(self, process_parser, spec_class, node, lane=None): - super(UserTaskParser, self).__init__(process_parser, spec_class, node, lane) - self.xpath = xpath_eval(node, extra_ns={'camunda': CAMUNDA_MODEL_NS}) + nsmap = DEFAULT_NSMAP.copy() + nsmap.update({'camunda': CAMUNDA_MODEL_NS}) + super(UserTaskParser, self).__init__(process_parser, spec_class, node, nsmap, lane) def create_task(self): form = self.get_form() diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/parser/BpmnDmnParser.py b/SpiffWorkflow/SpiffWorkflow/dmn/parser/BpmnDmnParser.py index f4fca29f..279023f8 100644 --- a/SpiffWorkflow/SpiffWorkflow/dmn/parser/BpmnDmnParser.py +++ b/SpiffWorkflow/SpiffWorkflow/dmn/parser/BpmnDmnParser.py @@ -1,19 +1,31 @@ import glob +import os -from ...bpmn.parser.util import xpath_eval +from lxml import etree + +from ...bpmn.parser.util import full_tag from ...bpmn.parser.ValidationException import ValidationException -from ...bpmn.parser.BpmnParser import BpmnParser -from ...dmn.parser.DMNParser import DMNParser +from ...bpmn.parser.BpmnParser import BpmnParser, BpmnValidator +from ...dmn.parser.DMNParser import DMNParser, get_dmn_ns from ..engine.DMNEngine import DMNEngine -from lxml import etree + +XSD_DIR = os.path.join(os.path.dirname(__file__), 'schema') +SCHEMAS = { + 'http://www.omg.org/spec/DMN/20151101/dmn.xsd': os.path.join(XSD_DIR, 'DMN.xsd'), + 'http://www.omg.org/spec/DMN/20180521/MODEL/': os.path.join(XSD_DIR, 'DMN12.xsd'), + 'https://www.omg.org/spec/DMN/20191111/MODEL/': os.path.join(XSD_DIR, 'DMN13.xsd'), +} + class BpmnDmnParser(BpmnParser): - def __init__(self): - super().__init__() + def __init__(self, namespaces=None, validator=None, dmn_schemas=None): + super().__init__(namespaces, validator) + self.dmn_schemas = dmn_schemas or SCHEMAS self.dmn_parsers = {} self.dmn_parsers_by_name = {} + self.dmn_dependencies = set() def get_engine(self, decision_ref, node): if decision_ref not in self.dmn_parsers: @@ -24,15 +36,23 @@ class BpmnDmnParser(BpmnParser): dmn_parser = self.dmn_parsers[decision_ref] dmn_parser.parse() decision = dmn_parser.decision + return DMNEngine(decision.decisionTables[0]) def add_dmn_xml(self, node, filename=None): """ Add the given lxml representation of the DMN file to the parser's set. """ - xpath = xpath_eval(node) - dmn_parser = DMNParser( - self, node, filename=filename, doc_xpath=xpath) + nsmap = get_dmn_ns(node) + # We have to create a dmn validator on the fly, because we support multiple versions + # If we have a bpmn validator, assume DMN validation should be done as well. + # I don't like this, but I don't see a better solution. + schema = self.dmn_schemas.get(nsmap.get('dmn')) + if self.validator and schema is not None: + validator = BpmnValidator(schema) + validator.validate(node, filename) + + dmn_parser = DMNParser(self, node, nsmap, filename=filename) self.dmn_parsers[dmn_parser.get_id()] = dmn_parser self.dmn_parsers_by_name[dmn_parser.get_name()] = dmn_parser @@ -59,3 +79,16 @@ class BpmnDmnParser(BpmnParser): self.add_dmn_xml(etree.parse(f).getroot(), filename=filename) finally: f.close() + + def get_dependencies(self): + return self.process_dependencies.union(self.dmn_dependencies) + + def get_dmn_dependencies(self): + return self.dmn_dependencies + + def _find_dependencies(self, process): + super()._find_dependencies(process) + parser_cls, cls = self._get_parser_class(full_tag('businessRuleTask')) + for business_rule in process.xpath('.//bpmn:businessRuleTask',namespaces=self.namespaces): + self.dmn_dependencies.add(parser_cls.get_decision_ref(business_rule)) + diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/parser/DMNParser.py b/SpiffWorkflow/SpiffWorkflow/dmn/parser/DMNParser.py index 79d97152..303fe64d 100644 --- a/SpiffWorkflow/SpiffWorkflow/dmn/parser/DMNParser.py +++ b/SpiffWorkflow/SpiffWorkflow/dmn/parser/DMNParser.py @@ -1,25 +1,28 @@ import ast +from SpiffWorkflow.bpmn.parser.node_parser import NodeParser, DEFAULT_NSMAP + from ...bpmn.parser.util import xpath_eval from ...dmn.specs.model import Decision, DecisionTable, InputEntry, \ OutputEntry, Input, Output, Rule - def get_dmn_ns(node): """ Returns the namespace definition for the current DMN :param node: the XML node for the DMN document """ + nsmap = DEFAULT_NSMAP.copy() if 'http://www.omg.org/spec/DMN/20151101/dmn.xsd' in node.nsmap.values(): - return 'http://www.omg.org/spec/DMN/20151101/dmn.xsd' + nsmap['dmn'] = 'http://www.omg.org/spec/DMN/20151101/dmn.xsd' + elif 'http://www.omg.org/spec/DMN/20180521/DI/' in node.nsmap.values(): + nsmap['dmn'] = 'http://www.omg.org/spec/DMN/20180521/DI/' elif 'https://www.omg.org/spec/DMN/20191111/MODEL/' in node.nsmap.values(): - return 'https://www.omg.org/spec/DMN/20191111/MODEL/' - return None + nsmap['dmn'] = 'https://www.omg.org/spec/DMN/20191111/MODEL/' + return nsmap - -class DMNParser(object): +class DMNParser(NodeParser): """ Please note this DMN Parser still needs a lot of work. A few key areas that need to be addressed: @@ -30,7 +33,7 @@ class DMNParser(object): DT_FORMAT = '%Y-%m-%dT%H:%M:%S' - def __init__(self, p, node, svg=None, filename=None, doc_xpath=None): + def __init__(self, p, node, nsmap, svg=None, filename=None): """ Constructor. @@ -40,14 +43,13 @@ class DMNParser(object): (optional) :param filename: the source BMN filename (optional) """ + super().__init__(node, nsmap, filename=filename) + self.parser = p self.node = node self.decision = None self.svg = svg self.filename = filename - self.doc_xpath = doc_xpath - self.dmn_ns = get_dmn_ns(self.node) - self.xpath = xpath_eval(self.node, {'dmn': self.dmn_ns}) def parse(self): self.decision = self._parse_decision(self.node.findall('{*}decision')) @@ -117,11 +119,12 @@ class DMNParser(object): def _parse_input(self, input_element): type_ref = None - xpath = xpath_eval(input_element, {'dmn': self.dmn_ns}) + prefix = self.nsmap['dmn'] + xpath = xpath_eval(input_element, {'dmn': prefix}) expression = None for input_expression in xpath('dmn:inputExpression'): type_ref = input_expression.attrib.get('typeRef', '') - expression_node = input_expression.find('{' + self.dmn_ns + '}text') + expression_node = input_expression.find('{' + prefix + '}text') if expression_node is not None: expression = expression_node.text diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DC.xsd b/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DC.xsd new file mode 100644 index 00000000..634ba13a --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DC.xsd @@ -0,0 +1,159 @@ + + + + + + + + + + + Color is a data type that represents a color value in the RGB format. + + + + + + + + + + + + + + + + A Point specifies an location in some x-y coordinate system. + + + + + + + + Dimension specifies two lengths (width and height) along the x and y axes in some x-y coordinate system. + + + + + + + + Bounds specifies a rectangular area in some x-y coordinate system that is defined by a location (x and y) and a size (width and height). + + + + + + + + + + AlignmentKind enumerates the possible options for alignment for layout purposes. + + + + + + + + + + + KnownColor is an enumeration of 17 known colors. + + + + + a color with a value of #800000 + + + + + a color with a value of #FF0000 + + + + + a color with a value of #FFA500 + + + + + a color with a value of #FFFF00 + + + + + a color with a value of #808000 + + + + + a color with a value of #800080 + + + + + a color with a value of #FF00FF + + + + + a color with a value of #FFFFFF + + + + + a color with a value of #00FF00 + + + + + a color with a value of #008000 + + + + + a color with a value of #000080 + + + + + a color with a value of #0000FF + + + + + a color with a value of #00FFFF + + + + + a color with a value of #008080 + + + + + a color with a value of #000000 + + + + + a color with a value of #C0C0C0 + + + + + a color with a value of #808080 + + + + + + diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMN.xsd b/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMN.xsd new file mode 100644 index 00000000..cf780022 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMN.xsd @@ -0,0 +1,438 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMN12.xsd b/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMN12.xsd new file mode 100644 index 00000000..47ade4ce --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMN12.xsd @@ -0,0 +1,504 @@ + + + + + + + Include the DMN Diagram Interchange (DI) schema + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMN13.xsd b/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMN13.xsd new file mode 100644 index 00000000..0593bfc1 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMN13.xsd @@ -0,0 +1,524 @@ + + + + + + + Include the DMN Diagram Interchange (DI) schema + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMNDI12.xsd b/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMNDI12.xsd new file mode 100644 index 00000000..9022a671 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMNDI12.xsd @@ -0,0 +1,115 @@ + + + + + + The Diagram Interchange (DI) package enables interchange of graphical information that language users have control over, such as position of nodes and line routing points. Language specifications specialize elements of DI to define diagram interchange elements for a language. + + + + + This element should never be instantiated directly, but rather concrete implementation should. It is placed there only to be referred in the sequence + + + + + + DiagramElement is the abstract super type of all elements in diagrams, including diagrams themselves. When contained in a diagram, diagram elements are laid out relative to the diagram's origin. + + + + + + + + + + + + an optional locally-owned style for this diagram element. + + + + + + a reference to an optional shared style element for this diagram element. + + + + + + + + + + + + the name of the diagram. + + + + + the documentation of the diagram. + + + + + the resolution of the diagram expressed in user units per inch. + + + + + + + + + + + + + the optional bounds of the shape relative to the origin of its nesting plane. + + + + + + + + + + + + + + an optional list of points relative to the origin of the nesting diagram that specifies the connected line segments of the edge + + + + + + + + + + Style contains formatting properties that affect the appearance or style of diagram elements, including diagram themselves. + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMNDI13.xsd b/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMNDI13.xsd new file mode 100644 index 00000000..4df284df --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/dmn/parser/schema/DMNDI13.xsd @@ -0,0 +1,108 @@ + + + + + + + + + + + This element should never be instantiated directly, but rather concrete implementation should. It is placed there only to be referred in the sequence + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/parser/__init__.py b/SpiffWorkflow/SpiffWorkflow/spiff/parser/__init__.py index d893298d..6b38b362 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/parser/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/parser/__init__.py @@ -1 +1 @@ -from .process import SpiffBpmnParser \ No newline at end of file +from .process import SpiffBpmnParser, VALIDATOR \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/parser/process.py b/SpiffWorkflow/SpiffWorkflow/spiff/parser/process.py index 2ec536f3..7e3dd169 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/parser/process.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/parser/process.py @@ -1,5 +1,7 @@ +import os + from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser -from SpiffWorkflow.bpmn.parser.BpmnParser import full_tag +from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnValidator, full_tag from SpiffWorkflow.bpmn.specs.events import StartEvent, EndEvent, IntermediateThrowEvent, BoundaryEvent, IntermediateCatchEvent from SpiffWorkflow.spiff.specs import NoneTask, ManualTask, UserTask, ScriptTask, SubWorkflowTask, TransactionSubprocess, CallActivity, ServiceTask @@ -11,6 +13,10 @@ from SpiffWorkflow.dmn.specs import BusinessRuleTask from SpiffWorkflow.spiff.parser.task_spec import BusinessRuleTaskParser +SPIFF_XSD = os.path.join(os.path.dirname(__file__), 'schema', 'spiffworkflow.xsd') +VALIDATOR = BpmnValidator(imports={'spiffworkflow': SPIFF_XSD}) + + class SpiffBpmnParser(BpmnDmnParser): OVERRIDE_PARSER_CLASSES = { @@ -31,3 +37,4 @@ class SpiffBpmnParser(BpmnDmnParser): full_tag('receiveTask'): (SpiffReceiveTaskParser, ReceiveTask), full_tag('businessRuleTask'): (BusinessRuleTaskParser, BusinessRuleTask) } + diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/parser/schema/spiffworkflow.xsd b/SpiffWorkflow/SpiffWorkflow/spiff/parser/schema/spiffworkflow.xsd new file mode 100644 index 00000000..8d781b24 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/spiff/parser/schema/spiffworkflow.xsd @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/spiff_task.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/spiff_task.py index fe497d13..04b89db7 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/spiff_task.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/specs/spiff_task.py @@ -39,6 +39,6 @@ class SpiffBpmnTask(BpmnSpecMixin): self.execute_script(my_task, self.prescript) def _on_complete_hook(self, my_task): + super()._on_complete_hook(my_task) if self.postscript is not None: self.execute_script(my_task, self.postscript) - super()._on_complete_hook(my_task) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py index cb788eb3..5a341188 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py @@ -3,6 +3,7 @@ import json import os import unittest +from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnValidator from SpiffWorkflow.task import TaskState @@ -18,9 +19,10 @@ class BpmnWorkflowTestCase(unittest.TestCase): serializer = BpmnWorkflowSerializer(wf_spec_converter) - def load_workflow_spec(self, filename, process_name): + def load_workflow_spec(self, filename, process_name, validate=True): f = os.path.join(os.path.dirname(__file__), 'data', filename) - parser = TestBpmnParser() + validator = BpmnValidator() if validate else None + parser = TestBpmnParser(validator=validator) parser.add_bpmn_files_by_glob(f) top_level_spec = parser.get_spec(process_name) subprocesses = parser.get_subprocess_specs(process_name) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ClashingName2Test.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ClashingName2Test.py deleted file mode 100644 index f906c848..00000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ClashingName2Test.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- - -import sys -import os -import unittest -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) - -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase -from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException -__author__ = 'kellym' - - - -class ClashingNameTest2(BpmnWorkflowTestCase): - """The example bpmn diagram tests both a set cardinality from user input - as well as looping over an existing array.""" - - def setUp(self): - pass - - def loadWorkflow(self): - self.load_workflow_spec('Approvals_bad.bpmn', 'Approvals') - - def testRunThroughHappy(self): - # make sure we raise an exception - # when validating a workflow with multiple - # same IDs in the BPMN workspace - self.assertRaises(ValidationException,self.loadWorkflow) - - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ClashingNameTest2) - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ProcessDependencyTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ProcessDependencyTest.py index 414a8abb..b3a79519 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ProcessDependencyTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ProcessDependencyTest.py @@ -1,18 +1,10 @@ # -*- coding: utf-8 -*- - - - -import sys import os import unittest from SpiffWorkflow.camunda.parser.CamundaParser import CamundaParser -from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser from SpiffWorkflow.spiff.parser import SpiffBpmnParser -from tests.SpiffWorkflow.bpmn.BpmnLoaderForTests import TestBpmnParser -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'danfunk' @@ -34,6 +26,7 @@ class ProcessDependencyTest(BpmnWorkflowTestCase): self.actual_test(SpiffBpmnParser()) def actual_test(self, parser): + # We ought to test the parsers in the packages they belong to, not here. filename = 'call_activity_nested' process_name = 'Level1' base_dir = os.path.join(os.path.dirname(__file__), 'data', filename) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Approvals.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Approvals.bpmn index 152f7785..1cc8d204 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Approvals.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Approvals.bpmn @@ -102,7 +102,7 @@ - + Supervisor_Approval @@ -147,7 +147,7 @@ - + Start3 diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Approvals_bad.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Approvals_bad.bpmn deleted file mode 100644 index 5e33ada6..00000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Approvals_bad.bpmn +++ /dev/null @@ -1,403 +0,0 @@ - - - - - - Person who takes the first action to start the process - - - - - - - - - - - Start1 - First_Approval_Wins - End1 - First_Approval_Wins_Done - Parallel_Approvals_Done - Parallel_SP - Parallel_SP_Done - - - - Supervisor_Approval__P_ - Gateway4 - Gateway5 - - - - Manager_Approval__P_ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Supervisor_Approval - Start2 - Supervisor_Approved - - - - Manager_Approval - Manager_Approved - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Start3 - Supervisor_Approval - End2 - - - - Manager_Approval - - - - Step1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/collaboration.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/collaboration.bpmn index 534949a9..3d02b4ba 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/collaboration.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/collaboration.bpmn @@ -2,31 +2,31 @@ - + - + - + lover_name - + - + - lover_name + lover_name - from_name + from_name - + Flow_1bl6jeh @@ -49,12 +49,12 @@ - - - - - - + + + + + + @@ -109,4 +109,4 @@ - \ No newline at end of file + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/correlation.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/correlation.bpmn index 769bd569..5319faf6 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/correlation.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/correlation.bpmn @@ -9,10 +9,10 @@ - task_num + task_num - init_id + init_id diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/correlation_two_conversations.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/correlation_two_conversations.bpmn index 2e52a62f..f9b1b9fa 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/correlation_two_conversations.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/correlation_two_conversations.bpmn @@ -13,18 +13,18 @@ - task_num + task_num - init_id + init_id - task_num + task_num - subprocess + subprocess diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/data_object.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/data_object.bpmn index a8f94029..298c5fa2 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/data_object.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/data_object.bpmn @@ -1,7 +1,6 @@ - Flow_18858hr diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/io_spec.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/io_spec.bpmn index 65356b56..dc10332f 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/io_spec.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/io_spec.bpmn @@ -9,6 +9,18 @@ + + + id_1 + id_2 + + + out_1 + out_2 + + Flow_0n038fc diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/ActionManagementTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/ActionManagementTest.py index 699e50c2..1324d027 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/ActionManagementTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/ActionManagementTest.py @@ -11,8 +11,8 @@ __author__ = 'matth' class ActionManagementTest(BpmnWorkflowTestCase): - START_TIME_DELTA=0.01 - FINISH_TIME_DELTA=0.02 + START_TIME_DELTA=0.05 + FINISH_TIME_DELTA=0.10 def now_plus_seconds(self, seconds): return datetime.datetime.now() + datetime.timedelta(seconds=seconds) @@ -27,7 +27,7 @@ class ActionManagementTest(BpmnWorkflowTestCase): self.assertEqual(1, len(self.workflow.get_tasks(TaskState.READY))) self.workflow.get_tasks(TaskState.READY)[0].set_data( start_time=start_time, finish_time=finish_time) - + def testRunThroughHappy(self): self.do_next_exclusive_step("Review Action", choice='Approve') self.workflow.do_engine_steps() diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/CallActivityEscalationTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/CallActivityEscalationTest.py index 1e19e43b..6c7ea6e2 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/CallActivityEscalationTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/CallActivityEscalationTest.py @@ -41,7 +41,7 @@ def track_workflow(wf_spec, completed_set): class CallActivityEscalationTest(BpmnWorkflowTestCase): def setUp(self): - self.spec, subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'CallActivity-Escalation-Test') + self.spec, subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'CallActivity-Escalation-Test', False) self.workflow = BpmnWorkflow(self.spec, subprocesses) def testShouldEscalate(self): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsSpTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsSpTest.py index f66c3ee3..0b93d1e6 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsSpTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsSpTest.py @@ -13,7 +13,7 @@ __author__ = 'matth' class MessageInterruptsSpTest(BpmnWorkflowTestCase): def setUp(self): - self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Message Interrupts SP') + self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Message Interrupts SP', False) def testRunThroughHappySaveAndRestore(self): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsTest.py index 55cfa8bf..bdd6d6c1 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsTest.py @@ -13,7 +13,7 @@ __author__ = 'matth' class MessageInterruptsTest(BpmnWorkflowTestCase): def setUp(self): - self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Test Workflows') + self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Test Workflows', False) def testRunThroughHappySaveAndRestore(self): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptTest.py index f7b278bb..3127cc78 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptTest.py @@ -13,7 +13,7 @@ __author__ = 'matth' class MessageNonInterruptTest(BpmnWorkflowTestCase): def setUp(self): - self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Test Workflows') + self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Test Workflows', False) def testRunThroughHappySaveAndRestore(self): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptsSpTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptsSpTest.py index d4192a23..a10f8e93 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptsSpTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptsSpTest.py @@ -13,7 +13,7 @@ __author__ = 'matth' class MessageNonInterruptsSpTest(BpmnWorkflowTestCase): def setUp(self): - self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Message Non Interrupt SP') + self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Message Non Interrupt SP', False) def testRunThroughHappySaveAndRestore(self): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessagesTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessagesTest.py index 486e53d2..2535daba 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessagesTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessagesTest.py @@ -13,7 +13,7 @@ __author__ = 'matth' class MessagesTest(BpmnWorkflowTestCase): def setUp(self): - self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Test Workflows') + self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Test Workflows', False) def testRunThroughHappy(self): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/CamundaParserTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/CamundaParserTest.py index 1b453449..21d9ab02 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/CamundaParserTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/CamundaParserTest.py @@ -1,8 +1,7 @@ from SpiffWorkflow.bpmn.parser.util import full_tag from SpiffWorkflow.camunda.specs.UserTask import UserTask from SpiffWorkflow.camunda.parser.CamundaParser import CamundaParser -from SpiffWorkflow.camunda.parser.UserTaskParser import UserTaskParser -from SpiffWorkflow.camunda.parser.business_rule_task import BusinessRuleTaskParser +from SpiffWorkflow.camunda.parser.task_spec import UserTaskParser, BusinessRuleTaskParser from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask from .BaseTestCase import BaseTestCase diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/serializer/UserTaskParserTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/UserTaskParserTest.py similarity index 96% rename from SpiffWorkflow/tests/SpiffWorkflow/camunda/serializer/UserTaskParserTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/camunda/UserTaskParserTest.py index 40d4a3aa..56c4f474 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/serializer/UserTaskParserTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/UserTaskParserTest.py @@ -1,6 +1,6 @@ import unittest -from SpiffWorkflow.camunda.parser.UserTaskParser import UserTaskParser +from SpiffWorkflow.camunda.parser.task_spec import UserTaskParser from tests.SpiffWorkflow.camunda.BaseTestCase import BaseTestCase diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/serializer/CamundaParserTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/serializer/CamundaParserTest.py deleted file mode 100644 index 29760e9d..00000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/serializer/CamundaParserTest.py +++ /dev/null @@ -1,28 +0,0 @@ -import unittest - -from SpiffWorkflow.camunda.parser.UserTaskParser import UserTaskParser -from SpiffWorkflow.camunda.specs.UserTask import UserTask -from SpiffWorkflow.camunda.parser.CamundaParser import CamundaParser - - -class CamundaParserTest(unittest.TestCase): - CORRELATE = CamundaParser - - def setUp(self): - self.parser = CamundaParser() - - def test_overrides(self): - expected_key = "{http://www.omg.org/spec/BPMN/20100524/MODEL}userTask" - self.assertIn(expected_key, - self.parser.OVERRIDE_PARSER_CLASSES) - - self.assertEqual((UserTaskParser, UserTask), - self.parser.OVERRIDE_PARSER_CLASSES.get(expected_key)) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(CamundaParserTest) - - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/DecisionRunner.py b/SpiffWorkflow/tests/SpiffWorkflow/dmn/DecisionRunner.py index 0c266bfd..e69c5db8 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/dmn/DecisionRunner.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/dmn/DecisionRunner.py @@ -4,7 +4,7 @@ from lxml import etree from SpiffWorkflow.bpmn.PythonScriptEngine import Box from SpiffWorkflow.dmn.engine.DMNEngine import DMNEngine -from SpiffWorkflow.dmn.parser.DMNParser import DMNParser +from SpiffWorkflow.dmn.parser.DMNParser import DMNParser, get_dmn_ns class Workflow: @@ -33,7 +33,7 @@ class DecisionRunner: with open(fn) as fh: node = etree.parse(fh) - self.dmnParser = DMNParser(None, node.getroot()) + self.dmnParser = DMNParser(None, node.getroot(), get_dmn_ns(node.getroot())) self.dmnParser.parse() decision = self.dmnParser.decision diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/Dmn20151101VersionTest.py b/SpiffWorkflow/tests/SpiffWorkflow/dmn/Dmn20151101VersionTest.py index 8ff40003..9a98ae2f 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/dmn/Dmn20151101VersionTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/dmn/Dmn20151101VersionTest.py @@ -12,7 +12,7 @@ class DmnVersionTest(BpmnWorkflowTestCase): def testLoad(self): dmn = os.path.join(os.path.dirname(__file__), 'data', - 'dmn_version_20191111_test.dmn') + 'dmn_version_20151101_test.dmn') self.assertIsNone(self.parser.add_dmn_file(dmn)) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/BaseTestCase.py b/SpiffWorkflow/tests/SpiffWorkflow/spiff/BaseTestCase.py index 248f84a1..9e882fcb 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/BaseTestCase.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/BaseTestCase.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import os -from SpiffWorkflow.spiff.parser import SpiffBpmnParser +from SpiffWorkflow.spiff.parser import SpiffBpmnParser, VALIDATOR from SpiffWorkflow.spiff.serializer import NoneTaskConverter, \ ManualTaskConverter, UserTaskConverter, ScriptTaskConverter, \ SubWorkflowTaskConverter, TransactionSubprocessConverter, \ @@ -28,9 +28,9 @@ class BaseTestCase(BpmnWorkflowTestCase): serializer = BpmnWorkflowSerializer(wf_spec_converter) - def load_workflow_spec(self, filename, process_name, dmn_filename=None): + def load_workflow_spec(self, filename, process_name, dmn_filename=None, validate=True): bpmn = os.path.join(os.path.dirname(__file__), 'data', filename) - parser = SpiffBpmnParser() + parser = SpiffBpmnParser(validator=VALIDATOR if validate else None) parser.add_bpmn_files_by_glob(bpmn) if dmn_filename is not None: dmn = os.path.join(os.path.dirname(__file__), 'data', 'dmn', dmn_filename) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/business_rule_task.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/business_rule_task.bpmn index 8b46dfc3..cb32afb7 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/business_rule_task.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/business_rule_task.bpmn @@ -5,53 +5,51 @@ Flow_0lrg65h - - Flow_0l8nhib - - - - - decision_1 - - - - Flow_1109ldv - Flow_0l8nhib - - Flow_0lrg65h - Flow_1109ldv + Flow_0m1tt51 question = "X" + + + + decision_1 + + Flow_0m1tt51 + Flow_19vr2vt + + + Flow_19vr2vt + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/correlation.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/correlation.bpmn index e1cca32c..e364fb1d 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/correlation.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/correlation.bpmn @@ -9,10 +9,10 @@ - num + num - init_id + init_id diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/correlation_two_conversations.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/correlation_two_conversations.bpmn index 4865a5c2..b42f5047 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/correlation_two_conversations.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/correlation_two_conversations.bpmn @@ -1,6 +1,6 @@ - + @@ -19,18 +19,18 @@ - topica_one + topica_one - payload_var_one.topica + payload_var_one.topica - topicb_one + topicb_one - payload_var_one.topicb + payload_var_one.topicb @@ -117,18 +117,18 @@ del time - topica_two + topica_two - topica_two + topica_two - topicb_two + topicb_two - topicb_two + topicb_two diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript.bpmn index ed0557cf..88fd74f0 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript.bpmn @@ -1,7 +1,6 @@ - Flow_1hjrex4 diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript_call_activity.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript_call_activity.bpmn index 864f9408..5e7e0978 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript_call_activity.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript_call_activity.bpmn @@ -4,6 +4,12 @@ + + in_data + + + out_data + Flow_1a4nkhi diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript_data_object.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript_data_object.bpmn index 8dce85b1..d6351af0 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript_data_object.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript_data_object.bpmn @@ -1,7 +1,6 @@ - Flow_1hjrex4 diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript_parent.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript_parent.bpmn index 9837fa8d..bd3e0c05 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript_parent.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/prescript_postscript_parent.bpmn @@ -1,7 +1,6 @@ - Flow_1e5oj0e diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/spiff_properties.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/spiff_properties.bpmn index 3e93a3ec..862dae78 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/spiff_properties.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/data/spiff_properties.bpmn @@ -1,7 +1,6 @@ - Flow_1hjrex4 @@ -16,7 +15,7 @@ - 3 + Flow_1hjrex4 Flow_1vlqqxh