ActiveObject.py 8.73 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# 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.
#
##############################################################################

import ExtensionClass
from AccessControl import ClassSecurityInfo
31
from Acquisition import aq_base
32
from ZODB.POSException import ConflictError
33
from Products.CMFCore.utils import getToolByName
34
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
35
from ActivityRuntimeEnvironment import getActivityRuntimeEnvironment
Jean-Paul Smets's avatar
Jean-Paul Smets committed
36

37 38 39 40 41
try:
  from Products.CMFCore import permissions
except ImportError:
  from Products.CMFCore import CMFCorePermissions as permissions

42
from zLOG import LOG, WARNING
43
import sys
Jean-Paul Smets's avatar
Jean-Paul Smets committed
44 45 46

DEFAULT_ACTIVITY = 'SQLDict'

47 48 49 50 51
# Processing node are used to store processing state or processing node
DISTRIBUTABLE_STATE = -1
INVOKE_ERROR_STATE = -2
VALIDATE_ERROR_STATE = -3
STOP_STATE = -4
52 53
# Special state which allows to select positive nodes
POSITIVE_NODE_STATE = 'Positive Node State'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
54 55

class ActiveObject(ExtensionClass.Base):
56 57 58 59 60 61 62
  """Active Object Mixin Class.

  Active object are objects whose methods are lazilly evaluated in the
  Activity Queue. To use an active object, you just have to call the
  method on the wrapper returned by the `activate` method like this:

  >>> obj.activate().aMethod()
63

64 65
  This will defer the call to obj.aMethod() 
  """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
66 67 68

  security = ClassSecurityInfo()

69 70 71
  def activate(self, activity=DEFAULT_ACTIVITY, active_process=None,
               passive_commit=0, activate_kw=None, **kw):
    """Returns an active wrapper for this object.
72

73
      Reserved Optional parameters:
74

75
      at_date           --  request execution date for this activate call
76

77 78 79
      after_method_id   --  never validate message if after_method_id
                            is in the list of methods which are
                            going to be executed
80

81 82 83
      after_message_uid --  never validate message if after_message_uid
                            is in the list of messages which are
                            going to be executed
84

85 86
      after_path        --  never validate message if after_path
                            is in the list of path which are
87
                            going to be executed
88

89 90 91 92 93 94 95 96 97
      after_path_and_method_id
                        -- never validate message if a message for
                           method_id on path is in the queue.

      tag               -- add a tag to a message

      after_tag         -- never validate message if there is a message
                           tagged with this tag.

98
    """
99 100
    # Get activate values from activate_kw, then _v_activate_kw
    # only if they are not set directly as arguments to activate()
101
    if activate_kw is not None:
102 103 104 105 106
      for k, v in activate_kw.iteritems():
        if k not in kw:
          kw[k] = v

    # Get default parameters from a transactional variable.
107
    tv = getTransactionalVariable(self)
108
    key = ('default_activate_parameter', id(aq_base(self)))
109 110 111 112 113 114 115 116
    try:
      for k, v in tv[key].iteritems():
        if k not in kw:
          kw[k] = v
    except KeyError:
      pass

    # Deprecated: This volatile variable '_v_activate_kw' can be used to pass parameters
117
    # automatically to activate.
118
    if getattr(self, '_v_activate_kw', None) is not None:
119 120 121 122 123 124 125 126
      import warnings
      warnings.warn('_v_activate_kw is deprecated;\n'
                    ' use setDefaultActivateParameters instead.',
                    DeprecationWarning)
      for k, v in self._v_activate_kw.iteritems():
        if k not in kw:
          kw[k] = v

127 128 129 130 131
    if kw.get('group_id', '') is None:
      raise ValueError, "Cannot defined a group_id with value None"
    elif kw.get('group_method_id') is None and kw.get('group_id') is not None:
      raise ValueError, "Cannot defined a group_id without group_method_id"
    
132
    activity_tool = getToolByName(self, 'portal_activities', None)
133
    if activity_tool is None: return self # Do nothing if no portal_activities
Jean-Paul Smets's avatar
Jean-Paul Smets committed
134 135 136
    # activate returns an ActiveWrapper
    # a queue can be provided as well as extra parameters
    # which can be used for example to define deferred tasks
137
    return activity_tool.activateObject(self, activity, active_process, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
138

139
  security.declareProtected( permissions.ModifyPortalContent, 'flushActivity' )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
140
  def flushActivity(self, invoke=0, **kw):
141
    activity_tool = getToolByName(self, 'portal_activities', None)
142
    if activity_tool is None: return # Do nothing if no portal_activities
Jean-Paul Smets's avatar
Jean-Paul Smets committed
143
    # flush all activities related to this object
144 145 146 147
    activity_tool.flush(self, invoke=invoke, **kw)

  security.declareProtected( permissions.ModifyPortalContent,
                             'recursiveFlushActivity' )
148
  def recursiveFlushActivity(self, invoke=0, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
149
    # flush all activities related to this object
150
    self.flushActivity(invoke=invoke, **kw)
151
    if getattr(aq_base(self), 'objectValues', None) is not None:
152
      for o in self.objectValues():
153
        if getattr(aq_base(o), 'recursiveFlushActivity', None) is not None:
154
          o.recursiveFlushActivity(invoke=invoke, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
155

156
  security.declareProtected( permissions.View, 'hasActivity' )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
157
  def hasActivity(self, **kw):
158
    """Tells if there is pending activities for this object.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
159
    """
160
    activity_tool = getToolByName(self, 'portal_activities', None)
161
    if activity_tool is None: return 0 # Do nothing if no portal_activities
Jean-Paul Smets's avatar
Jean-Paul Smets committed
162
    try:
163
      return activity_tool.hasActivity(self, **kw)
164 165
    except ConflictError:
      raise
Jean-Paul Smets's avatar
Jean-Paul Smets committed
166 167 168 169
    except:
      # If the portal_activities were not created
      # there can not be any activity
      return 0
170

171
  security.declareProtected( permissions.View, 'hasErrorActivity' )
172
  def hasErrorActivity(self, **kw):
173
    """Tells if there is failed activities for this object.
174 175 176
    """
    return self.hasActivity(processing_node = INVOKE_ERROR_STATE)

177
  security.declareProtected( permissions.View, 'hasInvalidActivity' )
178
  def hasInvalidActivity(self, **kw):
179
    """Tells if there is invalied activities for this object.
180 181 182
    """
    return self.hasActivity(processing_node = VALIDATE_ERROR_STATE)

183
  security.declareProtected( permissions.View, 'getActiveProcess' )
184
  def getActiveProcess(self):
185
    activity_tool = getToolByName(self, 'portal_activities', None)
186
    if activity_tool is None: return None # Do nothing if no portal_activities
187 188
    return activity_tool.getActiveProcess()

189 190 191 192
  security.declareProtected( permissions.ModifyPortalContent, 'setDefaultActivateParameters' )
  def setDefaultActivateParameters(self, **kw):
    # This method sets the default keyword parameters to activate. This is useful
    # when you need to specify special parameters implicitly (e.g. to reindexObject).
193
    tv = getTransactionalVariable(self)
194
    key = ('default_activate_parameter', id(aq_base(self)))
195 196 197
    tv[key] = kw

  security.declareProtected( permissions.View, 'getDefaultActivateParameterDict' )
198
  def getDefaultActivateParameterDict(self, inherit_placeless=True):
199 200
    # This method returns default activate parameters to self.
    # The result can be either a dict object or None.
201
    tv = getTransactionalVariable(self)
202
    if inherit_placeless:
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
      placeless = tv.get(('default_activate_parameter', ))
      if placeless is not None:
        placeless = placeless.copy()
    else:
      placeless = None
    local = tv.get(('default_activate_parameter', id(aq_base(self))))
    if local is None:
      result = placeless
    else:
      if placeless is None:
        result = local.copy()
      else:
        # local defaults takes precedence over placeless defaults.
        result = {}
        result.update(placeless)
        result.update(local)
    return result
220

221 222
  def getActivityRuntimeEnvironment(self):
    return getActivityRuntimeEnvironment()