diff --git a/ajax/dojo/macros.pt b/ajax/dojo/macros.pt index a1398e3..4ad58c0 100644 --- a/ajax/dojo/macros.pt +++ b/ajax/dojo/macros.pt @@ -5,10 +5,7 @@ diff --git a/process/README.txt b/process/README.txt new file mode 100644 index 0000000..7b13f2b --- /dev/null +++ b/process/README.txt @@ -0,0 +1,54 @@ +Business Process Management +=========================== + + ($Id$) + +We start with the definition of a simple process: + + startNode --> n01 --> endNode + + >>> from cybertools.process.definition import ProcessDefinition, Node, Transition + >>> process = ProcessDefinition() + >>> n01 = Node() + >>> process.startNode.addTransition(n01) + >>> n01.addTransition(process.endNode) + +Now let's execute the process: + + >>> from cybertools.process.execution import ProcessInstance + >>> instance = ProcessInstance(process) + >>> execution = instance.execute() + +As there aren't any interactions with the outside world in our process we +don't see anything. But we can check if the process instance has reached the +process' end node: + + >>> execution.currentNode is process.endNode + True + +So let's now associate an action handler with the process' nodes: + + >>> from zope.component import provideAdapter, adapts + >>> from zope.interface import implements + >>> from cybertools.process.interfaces import INode, IActionHandler + + >>> class DummyHandler(object): + ... implements(IActionHandler) + ... adapts(INode) + ... def __init__(self, context): pass + ... def handle(self, execution): + ... print 'working.' + + >>> provideAdapter(DummyHandler) + >>> execution = instance.execute() + working. + >>> execution.currentNode is process.startNode + True + >>> execution.trigger() + working. + >>> execution.currentNode is n01 + True + >>> execution.trigger() + working. + >>> execution.currentNode is process.endNode + True diff --git a/process/__init__.py b/process/__init__.py new file mode 100644 index 0000000..4bc90fb --- /dev/null +++ b/process/__init__.py @@ -0,0 +1,4 @@ +""" +$Id$ +""" + diff --git a/process/definition.py b/process/definition.py new file mode 100644 index 0000000..a4e5030 --- /dev/null +++ b/process/definition.py @@ -0,0 +1,116 @@ +# +# Copyright (c) 2006 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Process definitions. + +$Id$ +""" + +from zope import component +from zope.interface import implements +from cybertools.process.interfaces import INode, ITransition, IProcessDefinition +from cybertools.process.interfaces import IActionHandler + + +class Node(object): + + implements(INode) + + def __init__(self, process=None, name=u'', title=u'', handlerName=''): + self._process = process + self._incoming = set() + self._outgoing = set() + self._handlerName = handlerName + self.__name__ = name + self.title = title + + def getProcess(self): return self._process + def setProcess(self, prc): self._process = prc + process = property(getProcess, setProcess) + + @property + def outgoing(self): return self._outgoing + + @property + def incoming(self): return self._incoming + + def getHandlerName(self): return self._handlerName + def setHandlerName(self, name): self._handlerName = name + handlerName = property(getHandlerName, setHandlerName) + + def addTransition(self, destination): + transition = Transition(destination) + self.outgoing.add(transition) + transition.source = self + if transition.destination.process is None: + transition.destination.process = self.process + + def execute(self, execution): + execution.currentNode = self + handler = component.queryAdapter(self, IActionHandler, name=self.handlerName) + if handler is not None: + handler.handle(execution) # creates work item; work item triggers execution + else: + execution.trigger() + + +class Transition(object): + + implements(ITransition) + + def __init__(self, destination, qualifier=u''): + self._source = None + self._destination = destination + self._qualifier = qualifier + + def getSource(self): return self._source + def setSource(self, node): self._source = node + source = property(getSource, setSource) + + def getDestination(self): return self._destination + def setDestination(self, node): self._destination = node + destination = property(getDestination, setDestination) + + def getQualifier(self): return self._qualifier + def setQualifier(self, node): self._qualifier = node + qualifier = property(getQualifier, setQualifier) + + def take(self, execution): + self.destination.execute(execution) + + +class ProcessDefinition(object): + + implements(IProcessDefinition) + + def __init__(self): + self._instances = set() + self._startNode = Node(self) + self._endNode = Node(self) + + @property + def instances(self): return self._instances + + def getStartNode(self): return self._startNode + def setStartNode(self, node): self._startNode = node + startNode = property(getStartNode, setStartNode) + + def getEndNode(self): return self._endNode + def setEndNode(self, node): self._endNode = node + endNode = property(getEndNode, setEndNode) diff --git a/process/execution.py b/process/execution.py new file mode 100644 index 0000000..ed610b0 --- /dev/null +++ b/process/execution.py @@ -0,0 +1,74 @@ +# +# Copyright (c) 2006 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Execution of a process. + +$Id$ +""" + +from zope.interface import implements +from cybertools.process.interfaces import IExecution, IProcessInstance + + +class Execution(object): + + implements(IExecution) + + def __init__(self, instance): + self._instance = instance + self._currentNode = None + self._workItem = None + + @property + def instance(self): return self._instance + + def getCurrentNode(self): return self._currentNode + def setCurrentNode(self, node): self._currentNode = node + currentNode = property(getCurrentNode, setCurrentNode) + + def getWorkItem(self): return self._workItem + def setWorkItem(self, item): self._workItem = item + workItem = property(getWorkItem, setWorkItem) + + def trigger(self, transitionPattern=None): + outgoing = self.currentNode.outgoing + if outgoing: + transition = iter(outgoing).next() + transition.take(self) + + +class ProcessInstance(object): + + implements(IProcessInstance) + + def __init__(self, process): + self._process = process + self._executions = set() + + @property + def process(self): return self._process + + @property + def executions(self): return self._executions + + def execute(self): + execution = Execution(self) + self.executions.add(execution) + self.process.startNode.execute(execution) + return execution diff --git a/process/interfaces.py b/process/interfaces.py new file mode 100644 index 0000000..5bb5df0 --- /dev/null +++ b/process/interfaces.py @@ -0,0 +1,137 @@ +# +# Copyright (c) 2006 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Interfaces for process management. + +$Id$ +""" + +from zope.interface import Interface, Attribute +from zope import schema +from zope.i18nmessageid import MessageFactory + +_ = MessageFactory('zope') + + +# process/workflow definitions + +class INode(Interface): + """ A step of a process - typically a state that lets the process wait + for a user interaction or an action that is executed automatically. + """ + + process = Attribute('The process this node belongs to') + incoming = Attribute('Transitions that lead to this node') + outgoing = Attribute('Transitions that lead to the next nodes') + handlerName = Attribute('Name of an adapter that may handle the ' + 'execution of this node') + + def addTransition(destination): + """ Append a transition to the destination node given + to the collection of outgoing transitions. + """ + + def execute(execution): + """ Execute a node in an execution context of a process instance; + if this node signifies a wait state this will create a work item. + """ + + +class ITransition(Interface): + """ A transition leading from one node (activity, state, action) to + the next. + """ + source = Attribute('The node that triggered this transition') + destination = Attribute('The destination node of this transition') + qualifier = Attribute('A string giving a hint for the meaning of the ' + 'transition. May be used by an execution context ' + 'for deciding which transition it will transfer ' + 'control to') + + def take(execution): + """ Pass over the execution context from the source node to the + destination node. + """ + + +class IProcessDefinition(Interface): + """ The definition of a process or workflow. + """ + + instances = Attribute('A collection of process instances created from ' + 'this process definition') + startNode = Attribute('The start node of this process') + endNode = Attribute('The end node of this process') + + +class IActionHandler(Interface): + """ Will be called for handling process executions. Is typically + implemented as an adapter for INode. + """ + + def handle(execution): + """ Handles the execution of a node in the execution context given. + """ + +# process execution + + +class IExecution(Interface): + """ An execution context signifying the current state (or one of the + current states) of a process instance. + """ + + instance = Attribute('The process instance this execution context belongs to') + currentNode = Attribute('The node the process instance is currently in') + workItem = Attribute('The work item the process instance is currently ' + 'waiting for; None if the current node is not in a ' + 'waiting state') + + def trigger(transitionQualifiers=None): + """ A callback (handler) that will may be called by an action handler. + This will typically lead to moving on the execution context + to an outgoing transition of the current node. The execution + context will use the transitionQualifiers to decide which outgoing + transition(s) to transfer control to. + """ + + +class IProcessInstance(Interface): + """ An executing process, i.e. an execution context that keeps track of + the currently active node(s) of the process. + """ + + process = Attribute('The process definition this instance is created from') + executions = Attribute('A collection of currently active execution contexts') + + def execute(): + """ Start the execution of the process with its start node; + return the execution context. + """ + + +class IWorkItem(Interface): + """ An instance of an activity from a process definition. + """ + + node = Attribute('The node this work item has been created from') + execution = Attribute('The execution context (and thus the process ' + 'instance) that has create this work item') + + diff --git a/process/tests.py b/process/tests.py new file mode 100755 index 0000000..63fd6d9 --- /dev/null +++ b/process/tests.py @@ -0,0 +1,28 @@ +#! /usr/bin/python + +""" +Tests for the 'cybertools.process' package. + +$Id$ +""" + +import unittest, doctest +from zope.testing.doctestunit import DocFileSuite +from cybertools.process.definition import ProcessDefinition + +class TestProcess(unittest.TestCase): + "Basic tests for the process package." + + def testBasicStuff(self): + p = ProcessDefinition() + + +def test_suite(): + flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS + return unittest.TestSuite(( + unittest.makeSuite(TestProcess), + DocFileSuite('README.txt', optionflags=flags), + )) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/process/work.py b/process/work.py new file mode 100644 index 0000000..5e380e1 --- /dev/null +++ b/process/work.py @@ -0,0 +1,32 @@ +# +# Copyright (c) 2006 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Work items (tasks) and similar stuff. + +$Id$ +""" + +from zope.interface import implements +from cybertools.process.interfaces import IWorkItem + + +class WorkItem(object): + + implements(IWorkItem) +