Update of /cvs/gocept/AlphaFlow/activities
In directory amy:/tmp/cvs-serv12058/activities
Added Files:
gates.py routing.py
Log Message:
- advanced routing
=== Added File AlphaFlow/activities/gates.py ===
# Copyright (c) 2004 gocept gmbh & co. kg
# See also LICENSE.txt
# $Id: gates.py,v 1.1 2005/01/27 13:36:59 ctheune Exp $
"""Gates to support routing mechanisms
"""
# Sibling imports
from Products.AlphaFlow.workitem import registerWorkItem, BaseWorkItem
from Products.AlphaFlow.activity import registerActivity, BaseAutomaticActivity
from Products.AlphaFlow.exception import ConfigurationError
from Products.AlphaFlow.interfaces import IDaemonActivity
class GateActivity(BaseAutomaticActivity):
__implements__ = BaseAutomaticActivity.__implements__ + (IDaemonActivity,)
meta_type = "AlphaFlow Gate Activity"
activity_type = "gate"
mode = ""
mode_types = ('multi-merge', 'discriminate', 'synchronizing-merge')
_properties = BaseAutomaticActivity._properties + \
({'id': 'mode', 'type': 'selection', 'mode': 'w',
'select_variable': 'mode_types'},
)
# Discriminate is like "XOR" (trigger on first, ignore others)
# Multimerge is like "OR" (trigger on each)
# Synchronize is like "AND" (trigger once when all completed)
def configureFromDOMNode(self, node):
"""Configures the activity from a given DOM-Node"""
GateActivity.inheritedAttribute('configureFromDOMNode')(self, node)
if not node.hasAttribute("mode"):
raise ConfigurationError, "Mode required in %r" % (
self.activity_type, )
self.mode = node.getAttribute('mode').encode("ascii")
if self.mode not in self.mode_types:
raise ConfigurationError, "Mode must be one of %s for %r" % (
self.mode_types, self.activity_type)
class GateWorkItem(BaseWorkItem):
activity_type = "gate"
####################
# IWorkItem
def onStart(self):
self.open_routes = self._getAllRoutes()
def beforeCreationItems(self, items, parent):
vote_no = []
my_parent = self.getParent()
# Do the new items belong to a route?
route = self._findRouteForWI(parent)
if route is None:
return []
# Don't allow this gate to be generated again
for wi in items:
if wi == self.activity_id:
vote_no.append(wi)
self._rememberTrigger(parent, route)
return vote_no
def _findRouteForWI(self, workitem):
"""Identifies to which route this workitem belongs that leads to this
gate.
Returns one of self.getParent().getActivity().routes or None
"""
routing_start = self.getParent()
if not workitem.isChildOf(workitem=routing_start):
return None
possible_routes = [ self.getInstance()[x] for x in
routing_start.generated_workitems ]
for candidate in possible_routes:
if not candidate.state == "active":
continue
if not candidate.activity_id in routing_start.getActivity().routes:
continue
if (workitem == candidate) or \
workitem.isChildOf(workitem=candidate):
return candidate.activity_id
# Private methods
def _rememberTrigger(self, triggering_workitem, route):
mode = self.getActivity().mode
open_routes = self.open_routes
if not route in open_routes:
return
if mode == "multi-merge":
open_routes.remove(route)
self._doTrigger(triggering_workitem, route)
if len(open_routes) == 0:
self.changeState("complete", "Gate '%s' found by route '%s'" %
(self.activity_id, route))
elif mode == "discriminate":
if len(open_routes) == len(self._getAllRoutes()):
self._doTrigger(triggering_workitem, route)
self.changeState("complete", "Gate '%s' found by route '%s'" %
(self.activity_id, route))
open_routes.remove(route)
elif mode == "synchronizing-merge":
open_routes.remove(route)
if len(open_routes) == 0:
self._doTrigger(triggering_workitem, route)
self.changeState("complete", "Gate '%s' found by route '%s'" %
(self.activity_id, route))
else:
self.changeState("fallout", "Unknown gate mode: '%s'" % mode)
self.open_routes = open_routes
def _doTrigger(self, triggering_workitem, route):
open_routes = self.open_routes
self.createWorkItems(self.getActivity().continue_activity)
self.recordAction("Gate triggered",
"Route '%s' triggered '%s-gate' by workitem '%s'" %
(route, self.getActivity().mode, triggering_workitem.getId()))
def _getAllRoutes(self):
return list(tuple(self.getParent().getActivity().routes))
def estimateRelevantUsers(self):
return ""
# register the stuff
registerActivity(GateActivity)
registerWorkItem(GateWorkItem)
=== Added File AlphaFlow/activities/routing.py ===
# Copyright (c) 2004 gocept gmbh & co. kg
# See also LICENSE.txt
# $Id: routing.py,v 1.1 2005/01/27 13:36:59 ctheune Exp $
"""Complex routing mechanisms
"""
# Zope imports
from AccessControl import ClassSecurityInfo
# Sibling imports
from Products.AlphaFlow.workitem import registerWorkItem, BaseWorkItem
from Products.AlphaFlow.activity import registerActivity, BaseActivity
from Products.AlphaFlow import config
from Products.AlphaFlow.utils import killWorkItemRecursively
class RoutingActivity(BaseActivity):
"""Routing activity
A routing activity controls complex routing within a workflow over
multiple work items and work item branches.
The 'continue_activity' starts one or more routes. When a route reaches a
gate (by calling createWorkItems with a gate as an activity) the gate
notices that and might trigger. When a gate triggers all active routes and
all other active gates are terminated. Only routes and gates that are
offsprings from this routing activity will be controlled by the gates of
this routing activity.
"""
meta_type = "AlphaFlow Routing Activity"
activity_type = "route"
security = ClassSecurityInfo()
gates = ()
routes = ()
_properties = BaseActivity._properties + \
({'id': 'gates', 'type': 'multiple selection', 'mode': 'w',
'select_variable': 'listActivityIds'},
{'id': 'routes', 'type': 'multiple selection', 'mode': 'w',
'select_variable': 'listActivityIds'},
)
def configureFromDOMNode(self, node):
"""Configures the activity from a given DOM-Node"""
BaseActivity.configureFromDOMNode(self, node)
process = self.getParentNode()
for child in node.childNodes:
if child.nodeType in (child.TEXT_NODE,
child.COMMENT_NODE):
# Ignore whitespace
continue
act_id = child.getAttribute('id').encode('ascii')
act_type = child.nodeName
activity = process.addActivity(act_id, act_type)
activity.configureFromDOMNode(child)
if act_type == "gate":
self.gates = self.gates + (act_id,)
else:
self.routes = self.routes + (act_id,)
security.declareProtected(config.MANAGE_WORKFLOW, "getPossibleChildren")
def getPossibleChildren(self):
"""Return a list of possible following activities. (List of ids)"""
return self.routes
security.declareProtected(config.MANAGE_WORKFLOW,
"graphGetPossibleChildren")
def graphGetPossibleChildren(self):
"""Return a list of possible following activities. (List of ids)"""
acts = []
for act in self.routes:
acts.append({'id':act,
'label':'Route start'})
return acts
class RoutingWorkItem(BaseWorkItem):
activity_type = "route"
######################
# IWorkItem
def onStart(self):
"""Start the gate daemons and route work items
"""
self.createWorkItems(self.getActivity().gates)
self.createWorkItems(self.getActivity().routes)
def notifyWorkItemStateChange(self, workitem):
"""Check if routes have to be closed."""
# check if workitem is one of our gates
if not workitem.getId() in self.generated_workitems:
return
if not workitem.activity_type == "gate":
return
# check if gate is completed
if workitem.state != "complete":
return
finished_gate = workitem.activity_id
workitems = self.getGeneratedWorkItems()
# kill other gates
for wi in workitems:
if wi.activity_type != "gate":
continue
if wi.state != "active":
continue
wi.changeState("terminated", "Competing gate '%s' successfully "
"completed." % finished_gate)
# kill open routes
for wi in workitems:
if wi.activity_id == "gate":
continue
if wi.state != "active":
continue
killWorkItemRecursively(wi, "Competing route successfully "
"completed at gate '%s'." % finished_gate)
# complete routing
self.changeState("complete", "Gate %s completed" %
workitem.activity_id)
def getShortInfo(self):
"""Short information""" #XXX
return "Route is open"
def getStatusInfo(self):
"""Short status information""" # XXX
return "Route is open"
# register the stuff
registerActivity(RoutingActivity)
registerWorkItem(RoutingWorkItem)
|