예제 #1
0
class Foreach(FlowExpression):
    """Foreach activity."""

    allowed_child_types = _get_supported_activities()
    is_cond_allowed = True

    def __init__(self, parent_id, element, fei, context):
        """Constructor."""

        FlowExpression.__init__(self, parent_id, element, fei, context)
        # these are needed to reset local context upon every iteration
        self._parent_ctx = context
        self._element = element

    def _activate(self, msg):
        if self._is_start_message(msg):
            selection = self._parent_ctx
            for key in self._element.attrib["select"].split('.'):
                selection = selection.get(key)
            assert isinstance(selection, list), "%s is not list" % selection
            self.context.set('inst:selection', selection)
            self.context.set('inst:iteration', 1)
            if len(selection) > 0:
                self.context.set('inst:current', selection[0])

    def handle_message(self, channel, msg):
        """Handle message."""
        def guard():
            selection_size = len(self.context.get('inst:selection'))
            if self.context.get('inst:iteration') < selection_size:
                return True
            else:
                return False

        def restart():
            iteration = self.context.get('inst:iteration')
            selection = self.context.get('inst:selection')
            self.context = Context(self._parent_ctx)
            for child in self._element:
                if child.tag == 'context':
                    self.context.parse(child)
                    break
            self.reset_children()
            self.context.set('inst:iteration', iteration + 1)
            self.context.set('inst:current', selection[iteration])
            self.context.set('inst:selection', selection)
            channel.send(
                Message(name='start',
                        target=self.children[0].id,
                        origin=self.id))

        return FlowExpression.handle_message(self, channel, msg) or \
                self._activate(msg) or \
                self._was_activated(channel, msg, guard) or \
                self._was_sequence_completed(channel, msg,
                                             lambda: not guard(),
                                             restart) or \
                self._was_consumed_by_child(channel, msg) or 'ignored'
예제 #2
0
class Foreach(FlowExpression):
    """Foreach activity."""

    allowed_child_types = _get_supported_activities()
    is_cond_allowed = True

    def __init__(self, parent_id, element, fei, context):
        """Constructor."""

        FlowExpression.__init__(self, parent_id, element, fei, context)
        # these are needed to reset local context upon every iteration
        self._parent_ctx = context
        self._element = element

    def _activate(self, msg):
        if self._is_start_message(msg):
            selection = self._parent_ctx
            for key in self._element.attrib["select"].split('.'):
                selection = selection.get(key)
            assert isinstance(selection, list), "%s is not list" % selection
            self.context.set('inst:selection', selection)
            self.context.set('inst:iteration', 1)
            if len(selection) > 0:
                self.context.set('inst:current', selection[0])

    def handle_message(self, channel, msg):
        """Handle message."""

        def guard():
            selection_size = len(self.context.get('inst:selection'))
            if self.context.get('inst:iteration') < selection_size:
                return True
            else:
                return False

        def restart():
            iteration = self.context.get('inst:iteration')
            selection = self.context.get('inst:selection')
            self.context = Context(self._parent_ctx)
            for child in self._element:
                if child.tag == 'context':
                    self.context.parse(child)
                    break
            self.reset_children()
            self.context.set('inst:iteration', iteration + 1)
            self.context.set('inst:current', selection[iteration])
            self.context.set('inst:selection', selection)
            channel.send(Message(name='start', target=self.children[0].id,
                                 origin=self.id))

        return FlowExpression.handle_message(self, channel, msg) or \
                self._activate(msg) or \
                self._was_activated(channel, msg, guard) or \
                self._was_sequence_completed(channel, msg,
                                             lambda: not guard(),
                                             restart) or \
                self._was_consumed_by_child(channel, msg) or 'ignored'
예제 #3
0
class While(FlowExpression):
    """While activity."""

    allowed_child_types = _get_supported_activities()
    is_cond_allowed = True

    def __init__(self, parent_id, element, fei, context):
        """Constructor."""

        self.conditions = []
        FlowExpression.__init__(self, parent_id, element, fei, context)
        # these are needed to reset local context upon every iteration
        self._parent_ctx = context
        self._element = element

    def evaluate(self):
        """Check if conditions are met."""

        for cond in self.conditions:
            if eval(cond, {"context": self.context.as_dictionary()}):
                LOG.debug("Condition %s evaluated to True", cond)
                return True
            else:
                LOG.debug("Condition %s evaluated to False", cond)

        return False

    def handle_message(self, channel, msg):
        """Handle message."""
        def restart():
            self.context = Context(self._parent_ctx)
            for child in self._element:
                if child.tag == 'context':
                    self.context.parse(child)
                    break
            self.reset_children()
            channel.send(
                Message(name='start',
                        target=self.children[0].id,
                        origin=self.id))

        return FlowExpression.handle_message(self, channel, msg) or \
                self._was_activated(channel, msg, self.evaluate) or \
                self._was_sequence_completed(channel, msg,
                                             lambda: not self.evaluate(),
                                             restart) or \
                self._was_consumed_by_child(channel, msg) or 'ignored'
예제 #4
0
class While(FlowExpression):
    """While activity."""

    allowed_child_types = _get_supported_activities()
    is_cond_allowed = True

    def __init__(self, parent_id, element, fei, context):
        """Constructor."""

        self.conditions = []
        FlowExpression.__init__(self, parent_id, element, fei, context)
        # these are needed to reset local context upon every iteration
        self._parent_ctx = context
        self._element = element

    def evaluate(self):
        """Check if conditions are met."""

        for cond in self.conditions:
            if eval(cond, {"context": self.context.as_dictionary()}):
                LOG.debug("Condition %s evaluated to True", cond)
                return True
            else:
                LOG.debug("Condition %s evaluated to False", cond)

        return False

    def handle_message(self, channel, msg):
        """Handle message."""

        def restart():
            self.context = Context(self._parent_ctx)
            for child in self._element:
                if child.tag == 'context':
                    self.context.parse(child)
                    break
            self.reset_children()
            channel.send(Message(name='start', target=self.children[0].id,
                                 origin=self.id))

        return FlowExpression.handle_message(self, channel, msg) or \
                self._was_activated(channel, msg, self.evaluate) or \
                self._was_sequence_completed(channel, msg,
                                             lambda: not self.evaluate(),
                                             restart) or \
                self._was_consumed_by_child(channel, msg) or 'ignored'
예제 #5
0
class FlowExpression(object):
    """Flow expression."""

    allowed_child_types = ()
    is_ctx_allowed = True # TODO: refactor to introduce simple and complex activities
    is_cond_allowed = False
    is_handler = False

    def __init__(self, parent_id, element, fei, context):
        """Constructor."""

        self.fe_name = self.__class__.__name__.lower()
        LOG.debug("Creating %s", self.fe_name)

        self.state = 'ready'
        self.id = fei
        self.parent_id = parent_id
        self.children = []
        self.faults = None
        if self.is_ctx_allowed:
            self.context = Context(context)
        else:
            self.context = context

        el_index = 0
        for child in element:
            if child.tag in self.allowed_child_types:
                fexpr = _create_fe_from_element(self.id, child,
                                                "%s_%d" % (fei, el_index),
                                                self.context)
                self.children.append(fexpr)
                el_index = el_index + 1
            else:
                self._parse_non_child(child)

    def __str__(self):
        """String representation."""
        return "%s" % self.id

    def __repr__(self):
        """Instance representation."""
        return "<%s[%s, state='%s']>" % (self.__class__.__name__, self,
                                         self.state)

    def _parse_non_child(self, element):
        """Parse disallowed child element.

        Some flow expressions can contain child elements in their definition
        which are just attributes of them, not other flow expression.
        As an example consider <condition> inside <case>.
        """
        if element.tag == 'context' and self.is_ctx_allowed:
            self.context.parse(element)
            for child in element:
                if child.tag == 'faults':
                    self.faults = Faults(self.id, child, "%s_faults" % self.id,
                                         self.context)
                    break

        elif element.tag == 'condition' and self.is_cond_allowed:
            html_parser = HTMLParser()
            self.conditions.append(html_parser.unescape(element.text))
        else:
            raise FlowExpressionError("'%s' is disallowed child type" % \
                                      element.tag)

    def snapshot(self):
        """Return flow expression snapshot."""
        snapshot = {
            "id": self.id,
            "state": self.state,
            "type": self.fe_name,
            "children": [child.snapshot() for child in self.children]
        }
        if self.is_ctx_allowed:
            snapshot["context"] = self.context.localprops
            if self.faults:
                snapshot["faults"] = self.faults.snapshot()
        return snapshot

    def reset_state(self, state):
        """Reset activity's state."""
        LOG.debug("Resetting %s's state", self.fe_name)
        assert state["type"] == self.fe_name
        assert state["id"] == self.id
        self.state = state["state"]
        if self.is_ctx_allowed:
            self.context.localprops = state["context"]
            if self.faults:
                self.faults.reset_state(state["faults"])
        for child, childstate in zip(self.children, state["children"]):
            child.reset_state(childstate)

    def handle_message(self, channel, msg):
        """Handle message.

        This is common code used by all derived classes.
        """

        result = ''

        if self.state == 'completed':
            LOG.debug("%r is done already, %r is ignored", self, msg)
            result = 'ignored'
        elif msg.target is not None and \
                not msg.target.startswith(self.id):
            LOG.debug("%r is not for %r", msg, self)
            result = 'ignored'
        elif (self.is_ctx_allowed or self.is_handler) and \
                self.state == 'active' and \
                msg.name == 'fault' and msg.target == self.id:
            self.state = 'aborting'
            self.context.throw(code=msg.payload.get('code', 'GenericError'),
                               message=msg.payload.get('message', ''))
            all_in_final_state = True
            for child in self.children:
                if not is_state_final(child.state):
                    all_in_final_state = False
                    channel.send(Message(name='terminate', target=child.id,
                                         origin=self.id))
            if all_in_final_state:
                self.state = 'aborted'
                channel.send(Message(name='fault', target=self.parent_id,
                                     origin=self.id, payload=msg.payload))
            result = 'consumed'
        elif self.is_ctx_allowed and self.state == 'active' and \
                msg.name == 'terminate' and msg.target == self.id:
            self.state = 'aborting'
            for child in self.children:
                if not is_state_final(child.state):
                    channel.send(Message(name='terminate', target=child.id,
                                         origin=self.id))
            result = 'consumed'
        elif self.is_ctx_allowed and self.state == 'ready' and \
                msg.name == 'terminate' and msg.target == self.id:
            self.state = 'canceling'
            for child in self.children:
                if not is_state_final(child.state):
                    channel.send(Message(name='terminate', target=child.id,
                                         origin=self.id))
            result = 'consumed'
        elif (self.is_ctx_allowed or self.is_handler) and \
                self.state == 'aborting' and \
                (msg.name == 'canceled' or msg.name == 'aborted' or \
                 msg.name == 'completed') and \
                msg.target == self.id:
            if msg.name == 'completed' and msg.origin.endswith("faults"):
                self.state = 'completed'
                del self.context._props['inst:fault']
                channel.send(Message(name='completed', target=self.parent_id,
                                     origin=self.id))
            elif sum([int(is_state_final(child.state))
                    for child in self.children]) == len(self.children):
                # all children are in a final state
                if self.faults and self.faults.state == 'ready':
                    channel.send(Message(name='start', target=self.faults.id,
                                         origin=self.id))
                else:
                    self.state = 'aborted'
                    channel.send(Message(name='fault', target=self.parent_id,
                                         origin=self.id,
                                         payload=self.context.get('inst:fault')))
            result = 'consumed'
        elif self.is_ctx_allowed and self.state == 'aborting' and \
                msg.name == 'fault' and msg.target == self.id:
            self.state = 'aborted'
            channel.send(Message(name='fault', target=self.parent_id,
                                 origin=self.id,
                                 payload=self.context.get('inst:fault')))
            result = 'consumed'
        elif self.is_ctx_allowed and self.state == 'canceling' and \
                (msg.name == 'canceled' or msg.name == 'aborted' or \
                 msg.name == 'completed') and msg.target == self.id:
            if sum([int(is_state_final(child.state))
                    for child in self.children]) == len(self.children):
                # all children are in a final state
                channel.send(Message(name='canceled', target=self.parent_id,
                                     origin=self.id))
                self.state = 'canceled'
            result = 'consumed'
        elif not self.is_ctx_allowed and self.state == 'active' and \
                msg.name == 'terminate' and msg.target == self.id:
            self.state = 'aborted'
            channel.send(Message(name='aborted', target=self.parent_id,
                                 origin=self.id))
            result = 'consumed'
        elif not self.is_ctx_allowed and self.state == 'ready' and \
                msg.name == 'terminate' and msg.target == self.id:
            self.state = 'canceled'
            channel.send(Message(name='canceled', target=self.parent_id,
                                 origin=self.id))
            result = 'consumed'
        elif not self.is_ctx_allowed and self.state == 'aborting' and \
                msg.name == 'terminate' and msg.target == self.id:
            self.state = 'aborted'
            channel.send(Message(name='aborted', target=self.parent_id,
                                 origin=self.id))
            result = 'consumed'

        return result

    def _was_activated(self, channel, msg, guard=lambda: True):
        """Check if it's activation message."""

        if self._is_start_message(msg):

            if not guard():
                LOG.debug("Conditions for %r don't hold", self)
                self.state = 'completed'
                channel.send(Message(name='completed', origin=self.id,
                                     target=self.parent_id))
                return 'consumed'

            if len(self.children) > 0:
                self.state = 'active'
                channel.send(Message(name='start', origin=self.id,
                                     target=self.children[0].id))
            else:
                self.state = 'completed'
                channel.send(Message(name='completed', origin=self.id,
                                     target=self.parent_id))
            return 'consumed'
        else:
            return ''

    def _was_sequence_completed(self, channel, msg, guard=lambda: True,
                                compensate=lambda: None):
        """Check if all children in sequence were completed."""

        res = ''
        if self._is_complete_message(msg):
            for index, child in zip(range(0, len(self.children)),
                                    self.children):
                if child.id == msg.origin:
                    if (index + 1) < len(self.children):
                        channel.send(Message(name='start', origin=self.id,
                                             target="%s_%d" % (self.id,
                                                               index + 1)))
                    else:
                        if guard():
                            self.state = 'completed'
                            channel.send(Message(name='completed',
                                                 origin=self.id,
                                                 target=self.parent_id))
                        else:
                            compensate()
                    res = 'consumed'
                    break
        return res

    def _was_consumed_by_child(self, channel, msg):
        """Check if a child consumed the message."""

        for child in self.children:
            if msg.target.startswith(child.id):
                return child.handle_message(channel, msg)

        if self.state == 'aborting' and self.faults and \
           msg.target.startswith(self.faults.id):
            return self.faults.handle_message(channel, msg)

    def _is_start_message(self, msg):
        """Return True if the message destined to start the activity."""
        return self.state == 'ready' and msg.name == 'start' \
                and msg.target == self.id

    def _is_complete_message(self, msg):
        """Return True if the message is from completed child."""
        return self.state == 'active' and msg.name == 'completed' \
                and msg.target == self.id

    def reset_children(self):
        """Reset children state to ready."""

        for child in self.children:
            child.state = 'ready'
            child.reset_children()
예제 #6
0
class FlowExpression(object):
    """Flow expression."""

    allowed_child_types = ()
    is_ctx_allowed = True  # TODO: refactor to introduce simple and complex activities
    is_cond_allowed = False
    is_handler = False

    def __init__(self, parent_id, element, fei, context):
        """Constructor."""

        self.fe_name = self.__class__.__name__.lower()
        LOG.debug("Creating %s", self.fe_name)

        self.state = 'ready'
        self.id = fei
        self.parent_id = parent_id
        self.children = []
        self.faults = None
        if self.is_ctx_allowed:
            self.context = Context(context)
        else:
            self.context = context

        el_index = 0
        for child in element:
            if child.tag in self.allowed_child_types:
                fexpr = _create_fe_from_element(self.id, child,
                                                "%s_%d" % (fei, el_index),
                                                self.context)
                self.children.append(fexpr)
                el_index = el_index + 1
            else:
                self._parse_non_child(child)

    def __str__(self):
        """String representation."""
        return "%s" % self.id

    def __repr__(self):
        """Instance representation."""
        return "<%s[%s, state='%s']>" % (self.__class__.__name__, self,
                                         self.state)

    def _parse_non_child(self, element):
        """Parse disallowed child element.

        Some flow expressions can contain child elements in their definition
        which are just attributes of them, not other flow expression.
        As an example consider <condition> inside <case>.
        """
        if element.tag == 'context' and self.is_ctx_allowed:
            self.context.parse(element)
            for child in element:
                if child.tag == 'faults':
                    self.faults = Faults(self.id, child, "%s_faults" % self.id,
                                         self.context)
                    break

        elif element.tag == 'condition' and self.is_cond_allowed:
            html_parser = HTMLParser()
            self.conditions.append(html_parser.unescape(element.text))
        else:
            raise FlowExpressionError("'%s' is disallowed child type" % \
                                      element.tag)

    def snapshot(self):
        """Return flow expression snapshot."""
        snapshot = {
            "id": self.id,
            "state": self.state,
            "type": self.fe_name,
            "children": [child.snapshot() for child in self.children]
        }
        if self.is_ctx_allowed:
            snapshot["context"] = self.context.localprops
            if self.faults:
                snapshot["faults"] = self.faults.snapshot()
        return snapshot

    def reset_state(self, state):
        """Reset activity's state."""
        LOG.debug("Resetting %s's state", self.fe_name)
        assert state["type"] == self.fe_name
        assert state["id"] == self.id
        self.state = state["state"]
        if self.is_ctx_allowed:
            self.context.localprops = state["context"]
            if self.faults:
                self.faults.reset_state(state["faults"])
        for child, childstate in zip(self.children, state["children"]):
            child.reset_state(childstate)

    def handle_message(self, channel, msg):
        """Handle message.

        This is common code used by all derived classes.
        """

        result = ''

        if self.state == 'completed':
            LOG.debug("%r is done already, %r is ignored", self, msg)
            result = 'ignored'
        elif msg.target is not None and \
                not msg.target.startswith(self.id):
            LOG.debug("%r is not for %r", msg, self)
            result = 'ignored'
        elif (self.is_ctx_allowed or self.is_handler) and \
                self.state == 'active' and \
                msg.name == 'fault' and msg.target == self.id:
            self.state = 'aborting'
            self.context.throw(code=msg.payload.get('code', 'GenericError'),
                               message=msg.payload.get('message', ''))
            all_in_final_state = True
            for child in self.children:
                if not is_state_final(child.state):
                    all_in_final_state = False
                    channel.send(
                        Message(name='terminate',
                                target=child.id,
                                origin=self.id))
            if all_in_final_state:
                self.state = 'aborted'
                channel.send(
                    Message(name='fault',
                            target=self.parent_id,
                            origin=self.id,
                            payload=msg.payload))
            result = 'consumed'
        elif self.is_ctx_allowed and self.state == 'active' and \
                msg.name == 'terminate' and msg.target == self.id:
            self.state = 'aborting'
            for child in self.children:
                if not is_state_final(child.state):
                    channel.send(
                        Message(name='terminate',
                                target=child.id,
                                origin=self.id))
            result = 'consumed'
        elif self.is_ctx_allowed and self.state == 'ready' and \
                msg.name == 'terminate' and msg.target == self.id:
            self.state = 'canceling'
            for child in self.children:
                if not is_state_final(child.state):
                    channel.send(
                        Message(name='terminate',
                                target=child.id,
                                origin=self.id))
            result = 'consumed'
        elif (self.is_ctx_allowed or self.is_handler) and \
                self.state == 'aborting' and \
                (msg.name == 'canceled' or msg.name == 'aborted' or \
                 msg.name == 'completed') and \
                msg.target == self.id:
            if msg.name == 'completed' and msg.origin.endswith("faults"):
                self.state = 'completed'
                del self.context._props['inst:fault']
                channel.send(
                    Message(name='completed',
                            target=self.parent_id,
                            origin=self.id))
            elif sum([
                    int(is_state_final(child.state)) for child in self.children
            ]) == len(self.children):
                # all children are in a final state
                if self.faults and self.faults.state == 'ready':
                    channel.send(
                        Message(name='start',
                                target=self.faults.id,
                                origin=self.id))
                else:
                    self.state = 'aborted'
                    channel.send(
                        Message(name='fault',
                                target=self.parent_id,
                                origin=self.id,
                                payload=self.context.get('inst:fault')))
            result = 'consumed'
        elif self.is_ctx_allowed and self.state == 'aborting' and \
                msg.name == 'fault' and msg.target == self.id:
            self.state = 'aborted'
            channel.send(
                Message(name='fault',
                        target=self.parent_id,
                        origin=self.id,
                        payload=self.context.get('inst:fault')))
            result = 'consumed'
        elif self.is_ctx_allowed and self.state == 'canceling' and \
                (msg.name == 'canceled' or msg.name == 'aborted' or \
                 msg.name == 'completed') and msg.target == self.id:
            if sum([
                    int(is_state_final(child.state)) for child in self.children
            ]) == len(self.children):
                # all children are in a final state
                channel.send(
                    Message(name='canceled',
                            target=self.parent_id,
                            origin=self.id))
                self.state = 'canceled'
            result = 'consumed'
        elif not self.is_ctx_allowed and self.state == 'active' and \
                msg.name == 'terminate' and msg.target == self.id:
            self.state = 'aborted'
            channel.send(
                Message(name='aborted', target=self.parent_id, origin=self.id))
            result = 'consumed'
        elif not self.is_ctx_allowed and self.state == 'ready' and \
                msg.name == 'terminate' and msg.target == self.id:
            self.state = 'canceled'
            channel.send(
                Message(name='canceled', target=self.parent_id,
                        origin=self.id))
            result = 'consumed'
        elif not self.is_ctx_allowed and self.state == 'aborting' and \
                msg.name == 'terminate' and msg.target == self.id:
            self.state = 'aborted'
            channel.send(
                Message(name='aborted', target=self.parent_id, origin=self.id))
            result = 'consumed'

        return result

    def _was_activated(self, channel, msg, guard=lambda: True):
        """Check if it's activation message."""

        if self._is_start_message(msg):

            if not guard():
                LOG.debug("Conditions for %r don't hold", self)
                self.state = 'completed'
                channel.send(
                    Message(name='completed',
                            origin=self.id,
                            target=self.parent_id))
                return 'consumed'

            if len(self.children) > 0:
                self.state = 'active'
                channel.send(
                    Message(name='start',
                            origin=self.id,
                            target=self.children[0].id))
            else:
                self.state = 'completed'
                channel.send(
                    Message(name='completed',
                            origin=self.id,
                            target=self.parent_id))
            return 'consumed'
        else:
            return ''

    def _was_sequence_completed(self,
                                channel,
                                msg,
                                guard=lambda: True,
                                compensate=lambda: None):
        """Check if all children in sequence were completed."""

        res = ''
        if self._is_complete_message(msg):
            for index, child in zip(range(0, len(self.children)),
                                    self.children):
                if child.id == msg.origin:
                    if (index + 1) < len(self.children):
                        channel.send(
                            Message(name='start',
                                    origin=self.id,
                                    target="%s_%d" % (self.id, index + 1)))
                    else:
                        if guard():
                            self.state = 'completed'
                            channel.send(
                                Message(name='completed',
                                        origin=self.id,
                                        target=self.parent_id))
                        else:
                            compensate()
                    res = 'consumed'
                    break
        return res

    def _was_consumed_by_child(self, channel, msg):
        """Check if a child consumed the message."""

        for child in self.children:
            if msg.target.startswith(child.id):
                return child.handle_message(channel, msg)

        if self.state == 'aborting' and self.faults and \
           msg.target.startswith(self.faults.id):
            return self.faults.handle_message(channel, msg)

    def _is_start_message(self, msg):
        """Return True if the message destined to start the activity."""
        return self.state == 'ready' and msg.name == 'start' \
                and msg.target == self.id

    def _is_complete_message(self, msg):
        """Return True if the message is from completed child."""
        return self.state == 'active' and msg.name == 'completed' \
                and msg.target == self.id

    def reset_children(self):
        """Reset children state to ready."""

        for child in self.children:
            child.state = 'ready'
            child.reset_children()