From 3ab70b57e7cc1c858e6e10823cd98f5697417b1d Mon Sep 17 00:00:00 2001
From: Jean-Paul Smets <jp@nexedi.com>
Date: Fri, 9 Oct 2009 15:11:27 +0000
Subject: [PATCH] Refactored version of Interactor with compoenent sysytem

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@29545 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5Type/InitGenerator.py |  30 +++-
 product/ERP5Type/Interactor.py    | 251 ------------------------------
 product/ERP5Type/Utils.py         |  31 +++-
 product/ERP5Type/__init__.py      |  15 +-
 4 files changed, 68 insertions(+), 259 deletions(-)
 delete mode 100644 product/ERP5Type/Interactor.py

diff --git a/product/ERP5Type/InitGenerator.py b/product/ERP5Type/InitGenerator.py
index e8b44b4777..7351b3f2d6 100644
--- a/product/ERP5Type/InitGenerator.py
+++ b/product/ERP5Type/InitGenerator.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 ##############################################################################
 #
 # Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
@@ -35,6 +36,10 @@ from zLOG import LOG
 
 global product_document_registry
 product_document_registry = []
+global product_interactor_registry
+product_interactor_registry = []
+global interactor_class_id_registry 
+interactor_class_id_registry = {}
 
 def getProductDocumentPathList():
   result = product_document_registry
@@ -45,7 +50,16 @@ def InitializeDocument(document_class, document_path=None):
   global product_document_registry
   # Register class in ERP5Type.Document
   product_document_registry.append(((document_class, document_path)))
-  #LOG('InitializeDocument', 0, document_class.__name__)
+
+def getProductInteractorPathList():
+  result = product_interactor_registry
+  result.sort()
+  return result
+
+def InitializeInteractor(interactor_class, interactor_path=None):
+  global product_interactor_registry
+  # Register class in ERP5Type.Interactor
+  product_interactor_registry.append(((interactor_class, interactor_path)))
 
 def initializeProductDocumentRegistry():
   from Utils import importLocalDocument
@@ -56,3 +70,17 @@ def initializeProductDocumentRegistry():
     #LOG('Added product document to ERP5Type repository: %s (%s)' % (class_id, document_path), 0, '')
     #print 'Added product document to ERP5Type repository: %s (%s)' % (class_id, document_path)
     
+def initializeProductInteractorRegistry():
+  from Utils import importLocalInteractor
+  for (class_id, interactor_path) in product_interactor_registry:
+    if class_id != 'Interactor': # Base class can not be global and placeless
+      importLocalInteractor(class_id, path=interactor_path)
+
+def registerInteractorClass(class_id, klass):
+  global interactor_class_id_registry
+  interactor_class_id_registry[class_id] = klass
+
+def installInteractorClassRegistry():
+  global interactor_class_id_registry
+  for class_id, klass in interactor_class_id_registry.items():
+    klass().install()
diff --git a/product/ERP5Type/Interactor.py b/product/ERP5Type/Interactor.py
deleted file mode 100644
index 03f42a8d81..0000000000
--- a/product/ERP5Type/Interactor.py
+++ /dev/null
@@ -1,251 +0,0 @@
-from MethodObject import Method
-from Products.ERP5Type.Base import _aq_reset
-"""
-  Current implementation uses callable objects.
-  Using decorator would be more modern and consistent with
-  recent evolution of python. But we have 2.3 ERP5 users
-  so we should, at least, provide both implementations.
-
-  Code structure should be changed so that Interactors
-  because a new "type" of ERP5 class such Document
-  with a modular plugin structure.
-
-  TODO: multiple instances of interactors could
-  use different parameters. This way, interactions
-  can be defined on "instances" or can be
-  made generic.
-"""
-
-
-class InteractorMethodCall:
-  """
-  Method's wrapper used to keep arguments passed, in order to retrieve them
-  from before and after scripts if needed.
-  """
-
-  def __init__(self, method, instance, *args, **kw):
-    self.instance = instance
-    self.args = args
-    self.kw = kw
-    self.method = method
-
-  def __call__(self):
-    # We use self.__dict__['instance'] to prevent inserting the
-    # InteractorMethodCall instance in the acquisition chain
-    # which has some side effects
-    return self.method(self.__dict__['instance'], *self.args, **self.kw)
-
-class InteractorMethod(Method):
-
-  def __init__(self, method):
-    self.after_action_list = []
-    self.before_action_list = []
-    self.method = method
-    self.func_code = method.func_code
-    self.func_defaults = method.func_defaults
-    self.__name__ = method.__name__
-
-  def registerBeforeAction(self, action, args, kw):
-    self.before_action_list.append((action, args, kw))
-  
-  def registerAfterAction(self, action, args, kw):
-    self.after_action_list.append((action, args, kw))
-
-  def __call__(self, instance, *args, **kw):
-    method_call_object = InteractorMethodCall(self.method, instance, *args, **kw)
-    for action, args, kw in self.before_action_list:
-      action(method_call_object, *args, **kw)
-    result = method_call_object()
-    for action, args, kw in self.after_action_list:
-      action(method_call_object, *args, **kw)
-    return result
-
-class InteractorSource:
-
-  def __init__(self, method):
-    """
-      Register method
-    """
-    self.method = method
-
-  def doAfter(self, action, *args, **kw):
-    """
-    """
-    im_class = self.method.im_class
-    if not isinstance(self.method, InteractorMethod):
-      # Turn this into an InteractorMethod
-      interactor_method = InteractorMethod(self.method)
-      setattr(im_class, self.method.__name__, interactor_method)
-      self.method = interactor_method
-    # Register the action
-    self.method.registerAfterAction(action, args, kw)
-
-class Interactor:
-  def install(self):
-    raise NotImplementedError
-  
-  def uninstall(self):
-    raise NotImplementedError
-  
-  # Interaction implementation
-  def on(self, method):
-    """
-      Parameters may hold predicates ?
-      no need - use InteractorMethodCall and decide on action
-    """
-    return InteractorSource(method)
-
-
-class AqDynamicInteractor(Interactor):
-  """This base interactor will reset _aq_dynamic method cache, to regenerate
-  new accessors.
-  """
-  def resetAqDynamic(self, *args, **kw):
-    """
-      Reset _aq_dynamic
-    """
-    _aq_reset()
-
-
-class WorkflowToolInteractor(AqDynamicInteractor):
-  """This interactor reset aq_dynamic method cache whenever workflow
-  associations are changed.
-  """
-  def install(self):
-    from Products.CMFCore.WorkflowTool import WorkflowTool
-    self.on(WorkflowTool.manage_changeWorkflows).doAfter(self.resetAqDynamic)
-
-
-class DCWorkflowInteractor(AqDynamicInteractor):
-  """This interactor reset aq_dynamic method cache whenever a workflow
-  definition changes
-  """
-  def install(self):
-    from Products.DCWorkflow.Transitions import Transitions
-    self.on(Transitions.addTransition).doAfter(self.resetAqDynamic)
-    self.on(Transitions.deleteTransitions).doAfter(self.resetAqDynamic)
-    
-    from Products.DCWorkflow.Transitions import TransitionDefinition
-    self.on(TransitionDefinition.setProperties).doAfter(self.resetAqDynamic)
-    
-    from Products.DCWorkflow.Variables import Variables
-    self.on(Variables.setStateVar).doAfter(self.resetAqDynamic)
-
-## #
-## # Experimental part
-## #
-## class InteractionWorkflowAqDynamicInteractor(AqDynamicInteractor):
-
-##   def install(self):
-##     """
-##       Installs interactions
-##     """
-##     from Products.ERP5.Interaction import InteractionDefinition
-##     self.on(InteractionDefinition.setProperties).doAfter(self.resetAqDynamic, 1, 2, toto="foo")
-##     self.on(InteractionDefinition.addVariable).doAfter(self.resetAqDynamic, 1, 2, toto="foo")
-
-##   def uninstall(self):
-##     """
-##       Uninstall interactions
-##     """
-
-##   # Interaction example
-
-
-## class TypeInteractorExample(Interactor):
-##   def __init__(self, portal_type):
-##     self.portal_type = portal_type
-
-##   def install(self):
-##     from Products.CMFCore.TypesTool import TypesTool
-##     self.on(TypesTool.manage_edit).doAfter(self.doSomething)
-
-##   def doSomething(self, method_call_object):
-##     if self.portal_type == method_call_object.instance.portal_type:
-##       pass
-##       # do whatever
-
-
-## class InteractorOfInteractor(Interactor):
-
-##   def __init__(self, interactor):
-##     self.interactor = interactor
-
-##   def install(self):
-##     self.on(interactor.doSomething).doAfter(self.doSomething)
-
-##   def doSomething(self, method_call_object):
-##     pass
-
-## test = AqDynamicInteractor()
-## test.install()
-
-
-class FieldValueInteractor(Interactor):
-
-  def install(self):
-    """
-      Installs interactions
-    """
-    from Products.Formulator.Field import ZMIField
-    from Products.ERP5Form.ProxyField import ProxyField
-    from Products.Formulator.Form import ZMIForm
-    self.on(ZMIField.manage_edit).doAfter(self.purgeFieldValueCache)
-    self.on(ZMIField.manage_edit_xmlrpc).doAfter(self.purgeFieldValueCache)
-    self.on(ZMIField.manage_tales).doAfter(self.purgeFieldValueCache)
-    self.on(ZMIField.manage_tales_xmlrpc).doAfter(self.purgeFieldValueCache)
-    self.on(ProxyField.manage_edit).doAfter(self.purgeFieldValueCache)
-    self.on(ProxyField.manage_edit_target).doAfter(self.purgeFieldValueCache)
-    self.on(ProxyField.manage_tales).doAfter(self.purgeFieldValueCache)
-    self.on(ZMIForm.manage_renameObject).doAfter(self.purgeFieldValueCache)
-
-  def uninstall(self):
-    """
-      Uninstall interactions
-    """
-
-  def purgeFieldValueCache(self, method_call_object):
-    """
-    """
-    from Products.ERP5Form import Form, ProxyField
-    Form.purgeFieldValueCache()
-    ProxyField.purgeFieldValueCache()
-
-class TypeInteractorExample(Interactor):
-  def __init__(self, portal_type):
-    self.portal_type = portal_type
-
-  def install(self):
-    from Products.CMFCore.TypesTool import TypesTool
-    self.on(TypesTool.manage_edit).doAfter(self.doSomething)
-
-  def doSomething(self, method_call_object):
-    if self.portal_type == method_call_object.instance.portal_type:
-      pass
-      # do whatever
-
-class InteractorOfInteractor(Interactor):
-
-  def __init__(self, interactor):
-    self.interactor = interactor
-
-  def install(self):
-    self.on(Interactor.doSomething).doAfter(self.doSomething)
-
-  def doSomething(self, method_call_object):
-    pass
-
-
-#interactor_of_interactor = InteractorOfInteractor(test)
-#interactor_of_interactor.install()
-
-
-# Install some interactors:
-
-WorkflowToolInteractor().install()
-DCWorkflowInteractor().install()
-
-# This is used in ERP5Form and install method is called in ERP5Form
-# Don't install this interactor here.
-field_value_interactor = FieldValueInteractor()
-
diff --git a/product/ERP5Type/Utils.py b/product/ERP5Type/Utils.py
index 76ed69caf0..52769c7a2b 100644
--- a/product/ERP5Type/Utils.py
+++ b/product/ERP5Type/Utils.py
@@ -319,7 +319,7 @@ def getTranslationStringWithContext(self, msg_id, context, context_id):
 # Globals initialization
 #####################################################
 
-from InitGenerator import InitializeDocument
+from InitGenerator import InitializeDocument, InitializeInteractor, registerInteractorClass
 
 # List Regexp
 python_file_expr = re.compile("py$")
@@ -355,7 +355,7 @@ def updateGlobals(this_module, global_hook,
     this_module._dtmldir = os.path.join( product_path, 'dtml' )
 
     # Update PropertySheet Registry
-    for module_id in ('PropertySheet', 'interfaces', 'Constraint', ):
+    for module_id in ('PropertySheet', 'interfaces', 'Constraint'):
       path, module_id_list = getModuleIdList(product_path, module_id)
       if module_id == 'PropertySheet':
         import_method = importLocalPropertySheet
@@ -383,6 +383,12 @@ def updateGlobals(this_module, global_hook,
   path, module_id_list = getModuleIdList(product_path, 'Document')
   for document in module_id_list:
     InitializeDocument(document, document_path=path)
+
+  # Return interactor_class list
+  path, interactor_id_list = getModuleIdList(product_path, 'Interactor')
+  for interactor in interactor_id_list:
+    InitializeInteractor(interactor, interactor_path=path)
+
   return module_id_list + core_module_id_list
 
 #####################################################
@@ -567,6 +573,20 @@ def importLocalConstraint(class_id, path = None):
   finally:
     f.close()
 
+def importLocalInteractor(class_id, path=None):
+  import Products.ERP5Type.Interactor
+  if path is None:
+    instance_home = getConfiguration().instancehome
+    path = os.path.join(instance_home, "Interactor")
+  path = os.path.join(path, "%s.py" % class_id)
+  f = open(path)
+  try:
+    module = imp.load_source(class_id, path, f)
+    setattr(Products.ERP5Type.Interactor, class_id, getattr(module, class_id))
+    registerInteractorClass(class_id, getattr(Products.ERP5Type.Interactor, class_id))
+  finally:
+    f.close()
+
 def getLocalExtensionList():
   if not getConfiguration:
     return []
@@ -801,6 +821,7 @@ def importLocalDocument(class_id, document_path = None):
   import Products.ERP5Type.Document
   import Permissions
   import Products
+
   if document_path is None:
     instance_home = getConfiguration().instancehome
     path = os.path.join(instance_home, "Document")
@@ -934,6 +955,10 @@ def initializeLocalPropertySheetRegistry():
 def initializeLocalConstraintRegistry():
   initializeLocalRegistry("Constraint", importLocalConstraint)
 
+def initializeLocalInteractorRegistry():
+  initializeLocalRegistry("Interactor", importLocalInteractor)
+
+
 #####################################################
 # Product initialization
 #####################################################
@@ -942,7 +967,7 @@ def initializeProduct( context,
                        this_module,
                        global_hook,
                        document_module=None,
-                       document_classes=None,
+                       document_classes=None, # XXX - Never used - must be likely removed
                        object_classes=None,
                        portal_tools=None,
                        content_constructors=None,
diff --git a/product/ERP5Type/__init__.py b/product/ERP5Type/__init__.py
index 96705f7aac..b6952dc745 100644
--- a/product/ERP5Type/__init__.py
+++ b/product/ERP5Type/__init__.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 ##############################################################################
 #
 # Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
@@ -29,9 +30,9 @@
     ERP5Type is provides a RAD environment for Zope / CMF
     All ERP5 classes derive from ERP5Type
 """
+
 # Switch(es) for ongoing development which require single code base
 USE_BASE_TYPE = False
-USE_INTERACTOR = False
 
 # Update ERP5 Globals
 import sys, Permissions, os
@@ -99,9 +100,15 @@ def initialize( context ):
   # We should register local classes at some point
   from Products.ERP5Type.Utils import initializeLocalDocumentRegistry
   initializeLocalDocumentRegistry()
-  # Experimental Interactor
-  if USE_INTERACTOR:
-    import Interactor
+  # We can now setup global interactors
+  from Products.ERP5Type.InitGenerator import initializeProductInteractorRegistry
+  initializeProductInteractorRegistry()
+  # And local interactors
+  from Products.ERP5Type.Utils import initializeLocalInteractorRegistry
+  initializeLocalInteractorRegistry()
+  # We can now install all interactors
+  from Products.ERP5Type.InitGenerator import installInteractorClassRegistry
+  installInteractorClassRegistry()
 
 from AccessControl.SecurityInfo import allow_module
 from AccessControl.SecurityInfo import ModuleSecurityInfo
-- 
2.30.9