From 26891225a79886c4bfcbdd03d1b1da79a612f702 Mon Sep 17 00:00:00 2001
From: Kazuhiko Shiozaki <kazuhiko@nexedi.com>
Date: Mon, 17 Feb 2014 14:09:08 +0100
Subject: [PATCH] use minitage.recipe.egg more to manage our patched eggs.

---
 .../egg-patch/Acquisition/aq_dynamic.patch    | 292 ++++++++++++++++++
 .../Products.DCWorkflow/workflow_method.patch | 120 +++++++
 stack/erp5/buildout.cfg                       |  21 +-
 3 files changed, 426 insertions(+), 7 deletions(-)
 create mode 100644 component/egg-patch/Acquisition/aq_dynamic.patch
 create mode 100644 component/egg-patch/Products.DCWorkflow/workflow_method.patch

diff --git a/component/egg-patch/Acquisition/aq_dynamic.patch b/component/egg-patch/Acquisition/aq_dynamic.patch
new file mode 100644
index 000000000..deb801fcf
--- /dev/null
+++ b/component/egg-patch/Acquisition/aq_dynamic.patch
@@ -0,0 +1,292 @@
+diff -uNr Acquisition-2.13.8/src/Acquisition/_Acquisition.c Acquisition-2.13.8nxd001/src/Acquisition/_Acquisition.c
+--- Acquisition-2.13.8/src/Acquisition/_Acquisition.c	2011-06-11 17:19:14.000000000 +0200
++++ Acquisition-2.13.8nxd001/src/Acquisition/_Acquisition.c	2013-10-31 16:24:55.665085888 +0100
+@@ -448,6 +448,64 @@
+ }
+ 
+ static PyObject *
++Wrapper_GetAttr(PyObject *self, PyObject *attr_name, PyObject *orig)
++{
++  /* This function retrieves an attribute from an object by PyObject_GetAttr.
++
++     The main difference between Wrapper_GetAttr and PyObject_GetAttr is that
++     Wrapper_GetAttr calls _aq_dynamic to generate an attribute dynamically, if
++     the attribute is not found.
++  */
++  PyObject *r, *v, *tb;
++  PyObject *d, *m;
++  PyObject *o;
++
++  if (isWrapper (self))
++    o = WRAPPER(self)->obj;
++  else
++    o = self;
++  
++  /* Try to get an attribute in the normal way first.  */
++  r = PyObject_GetAttr(o, attr_name);
++  if (r)
++    return r;
++
++  /* If an unexpected error happens, return immediately.  */
++  PyErr_Fetch(&r,&v,&tb);
++  if (r != PyExc_AttributeError)
++    {
++      PyErr_Restore(r,v,tb);
++      return NULL;
++    }
++
++  /* Try to get _aq_dynamic.  */
++  m = PyObject_GetAttrString(o, "_aq_dynamic");
++  if (! m) {
++    PyErr_Restore(r,v,tb);
++    return NULL;
++  }
++
++  /* Call _aq_dynamic in the context of the original acquisition wrapper.  */
++  if (PyECMethod_Check(m) && PyECMethod_Self(m)==o)
++    ASSIGN(m,PyECMethod_New(m,OBJECT(self)));
++  else if (has__of__(m)) ASSIGN(m,__of__(m,OBJECT(self)));
++  d = PyObject_CallFunction(m, "O", attr_name);
++  Py_DECREF(m);
++
++  /* In the case of None, assume that the attribute is not found.  */
++  if (d == Py_None) {
++    Py_DECREF(d);
++    PyErr_Restore(r,v,tb);
++    return NULL;
++  }
++
++  Py_XDECREF(r);
++  Py_XDECREF(v);
++  Py_XDECREF(tb);
++  return d;
++}
++
++static PyObject *
+ Wrapper_acquire(Wrapper *self, PyObject *oname, 
+ 		PyObject *filter, PyObject *extra, PyObject *orig,
+ 		int explicit, int containment);
+@@ -545,8 +603,8 @@
+ 	  Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb);
+ 	  r=NULL;
+ 	}
+-	  /* normal attribute lookup */
+-      else if ((r=PyObject_GetAttr(self->obj,oname)))
++      /* Give _aq_dynamic a chance, then normal attribute lookup */
++      else if ((r=Wrapper_GetAttr(OBJECT(self),oname,orig)))
+ 	{
+ 	  if (r==Acquired)
+ 	    {
+@@ -670,7 +728,7 @@
+           Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb);
+           r=NULL;
+ 
+-	  if ((r=PyObject_GetAttr(self->container,oname))) {
++	  if ((r=Wrapper_GetAttr(self->container,oname,orig))) {
+ 	    if (r == Acquired) {
+ 	      Py_DECREF(r);
+ 	    }
+@@ -707,7 +765,7 @@
+ Wrapper_getattro(Wrapper *self, PyObject *oname)
+ {
+   if (self->obj || self->container)
+-    return Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 1, 0, 0);
++    return Wrapper_findattr(self, oname, NULL, NULL, OBJECT(self), 1, 1, 0, 0);
+ 
+   /* Maybe we are getting initialized? */
+   return Py_FindAttr(OBJECT(self),oname);
+@@ -724,7 +782,7 @@
+     return Py_FindAttr(OBJECT(self),oname);
+ 
+   if (self->obj || self->container)
+-    return Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 0, 0, 0);
++    return Wrapper_findattr(self, oname, NULL, NULL, OBJECT(self), 1, 0, 0, 0);
+ 
+   /* Maybe we are getting initialized? */
+   return Py_FindAttr(OBJECT(self),oname);
+diff -uNr Acquisition-2.13.8/src/Acquisition/test_dynamic_acquisition.py Acquisition-2.13.8nxd001/src/Acquisition/test_dynamic_acquisition.py
+--- Acquisition-2.13.8/src/Acquisition/test_dynamic_acquisition.py	1970-01-01 01:00:00.000000000 +0100
++++ Acquisition-2.13.8nxd001/src/Acquisition/test_dynamic_acquisition.py	2013-10-31 16:24:55.665085888 +0100
+@@ -0,0 +1,160 @@
++##############################################################################
++#
++# Copyright (c) 1996-2002 Zope Corporation and Contributors.
++# All Rights Reserved.
++#
++# This software is subject to the provisions of the Zope Public License,
++# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
++# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
++# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
++# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
++# FOR A PARTICULAR PURPOSE
++#
++##############################################################################
++import Acquisition
++
++def checkContext(self, o):
++    # Python equivalent to aq_inContextOf
++    from Acquisition import aq_base, aq_parent, aq_inner
++    subob = self
++    o = aq_base(o)
++    while 1:
++        if aq_base(subob) is o:
++            return True
++        self = aq_inner(subob)
++        if self is None: break
++        subob = aq_parent(self)
++        if subob is None: break
++    return False
++
++class B(Acquisition.Implicit):
++    color='red'
++
++    def __init__(self, name='b'):
++        self.name = name
++
++    def _aq_dynamic(self, attr):
++        if attr == 'bonjour': return None
++
++        def dynmethod():
++            chain = ' <- '.join(repr(obj) for obj in Acquisition.aq_chain(self))
++            print repr(self) + '.' + attr
++            print 'chain:', chain
++
++        return dynmethod
++
++    def __repr__(self):
++        return "%s(%r)" % (self.__class__.__name__, self.name)
++
++class A(Acquisition.Implicit):
++
++    def __init__(self, name='a'):
++        self.name = name
++
++    def hi(self):
++        print self, self.color
++
++    def _aq_dynamic(self, attr):
++        return None
++
++    def __repr__(self):
++        return "%s(%r)" % (self.__class__.__name__, self.name)
++
++def test_dynamic():
++    r'''
++    The _aq_dynamic functionality allows an object to dynamically provide an
++    attribute.
++    
++    If an object doesn't have an attribute, Acquisition checks to see if the
++    object has a _aq_dynamic method, which is then called. It is functionally
++    equivalent to __getattr__, but _aq_dynamic is called with 'self' as the
++    acquisition wrapped object where as __getattr__ is called with self as the
++    unwrapped object.
++
++    Let's see how this works. In the examples below, the A class defines
++    '_aq_dynamic', but returns 'None' for all attempts, which means that no new
++    attributes should be generated dynamically. It also doesn't define 'color'
++    attribute, even though it uses it in the 'hi' method.
++
++        >>> A().hi()
++        Traceback (most recent call last):
++        ...
++        AttributeError: color
++    
++    The class B, on the other hand, generates all attributes dynamically,
++    except if it is called 'bonjour'.
++    
++    First we need to check that, even if an object provides '_aq_dynamic',
++    "regular" Aquisition attribute access should still work:
++
++        >>> b=B()
++        >>> b.a=A()
++        >>> b.a.hi()
++        A('a') red
++        >>> b.a.color='green'
++        >>> b.a.hi()
++        A('a') green
++
++    Now, let's see some dynamically generated action. B does not define a
++    'salut' method, but remember that it dynamically generates a method for
++    every attribute access:
++
++        >>> b.a.salut()
++        B('b').salut
++        chain: B('b')
++
++        >>> a=A('a1')
++        >>> a.b=B('b1')
++        >>> a.b.salut()
++        B('b1').salut
++        chain: B('b1') <- A('a1')
++
++        >>> b.a.bonjour()
++        Traceback (most recent call last):
++        ...
++        AttributeError: bonjour
++
++        >>> a.b.bonjour()
++        Traceback (most recent call last):
++        ...
++        AttributeError: bonjour
++
++    '''
++
++def test_wrapper_comparissons():
++    r'''
++
++    Test wrapper comparisons in presence of _aq_dynamic
++
++        >>> b=B()
++        >>> b.a=A()
++        >>> foo = b.a
++        >>> bar = b.a
++        >>> assert( foo == bar )
++        >>> c = A('c')
++        >>> b.c = c
++        >>> b.c.d = c
++        >>> b.c.d == c
++        True
++        >>> b.c.d == b.c
++        True
++        >>> b.c == c
++        True
++
++    Test contextuality in presence of _aq_dynamic
++
++        >>> checkContext(b.c, b)
++        True
++        >>> checkContext(b.c, b.a)
++        False
++
++        >>> assert b.a.aq_inContextOf(b)
++        >>> assert b.c.aq_inContextOf(b)
++        >>> assert b.c.d.aq_inContextOf(b)
++        >>> assert b.c.d.aq_inContextOf(c)
++        >>> assert b.c.d.aq_inContextOf(b.c)
++        >>> assert not b.c.aq_inContextOf(foo)
++        >>> assert not b.c.aq_inContextOf(b.a)
++        >>> assert not b.a.aq_inContextOf('somestring')
++'''
++
+diff -uNr Acquisition-2.13.8/src/Acquisition/tests.py Acquisition-2.13.8nxd001/src/Acquisition/tests.py
+--- Acquisition-2.13.8/src/Acquisition/tests.py	2011-06-11 17:09:38.000000000 +0200
++++ Acquisition-2.13.8nxd001/src/Acquisition/tests.py	2013-10-31 16:24:55.669085888 +0100
+@@ -2552,6 +2552,7 @@
+ def test_suite():
+     return unittest.TestSuite((
+         DocTestSuite(),
++        DocTestSuite('Acquisition.test_dynamic_acquisition'),
+         DocFileSuite('README.txt', package='Acquisition'),
+         unittest.makeSuite(TestParent),
+         unittest.makeSuite(TestAcquire),
+diff -uNr Acquisition-2.13.8/src/Acquisition.egg-info/SOURCES.txt Acquisition-2.13.8nxd001/src/Acquisition.egg-info/SOURCES.txt
+--- Acquisition-2.13.8/src/Acquisition.egg-info/SOURCES.txt	2011-06-11 17:21:18.000000000 +0200
++++ Acquisition-2.13.8nxd001/src/Acquisition.egg-info/SOURCES.txt	2013-10-31 16:24:55.669085888 +0100
+@@ -15,6 +15,7 @@
+ src/Acquisition/_Acquisition.c
+ src/Acquisition/__init__.py
+ src/Acquisition/interfaces.py
++src/Acquisition/test_dynamic_acquisition.py
+ src/Acquisition/tests.py
+ src/Acquisition.egg-info/PKG-INFO
+ src/Acquisition.egg-info/SOURCES.txt
diff --git a/component/egg-patch/Products.DCWorkflow/workflow_method.patch b/component/egg-patch/Products.DCWorkflow/workflow_method.patch
new file mode 100644
index 000000000..cdf243ab9
--- /dev/null
+++ b/component/egg-patch/Products.DCWorkflow/workflow_method.patch
@@ -0,0 +1,120 @@
+diff -uNr Products.DCWorkflow-2.2.4/Products/DCWorkflow/DCWorkflow.py Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/DCWorkflow.py
+--- Products.DCWorkflow-2.2.4/Products/DCWorkflow/DCWorkflow.py	2011-11-01 18:55:01.000000000 +0100
++++ Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/DCWorkflow.py	2013-10-31 16:42:05.021141352 +0100
+@@ -40,6 +40,7 @@
+ from Products.DCWorkflow.permissions import ManagePortal
+ from Products.DCWorkflow.Transitions import TRIGGER_AUTOMATIC
+ from Products.DCWorkflow.Transitions import TRIGGER_USER_ACTION
++from Products.DCWorkflow.Transitions import TRIGGER_WORKFLOW_METHOD
+ from Products.DCWorkflow.utils import Message as _
+ from Products.DCWorkflow.utils import modifyRolesForGroup
+ from Products.DCWorkflow.utils import modifyRolesForPermission
+@@ -281,6 +282,52 @@
+             raise Unauthorized(action)
+         self._changeStateOf(ob, tdef, kw)
+ 
++    security.declarePrivate('isWorkflowMethodSupported')
++    def isWorkflowMethodSupported(self, ob, method_id):
++        '''
++        Returns a true value if the given workflow method
++        is supported in the current state.
++        '''
++        sdef = self._getWorkflowStateOf(ob)
++        if sdef is None:
++            return 0
++        if method_id in sdef.transitions:
++            tdef = self.transitions.get(method_id, None)
++            if (tdef is not None and
++                tdef.trigger_type == TRIGGER_WORKFLOW_METHOD and
++                self._checkTransitionGuard(tdef, ob)):
++                return 1
++        return 0
++
++    security.declarePrivate('wrapWorkflowMethod')
++    def wrapWorkflowMethod(self, ob, method_id, func, args, kw):
++        '''
++        Allows the user to request a workflow action.  This method
++        must perform its own security checks.
++        '''
++        sdef = self._getWorkflowStateOf(ob)
++        if sdef is None:
++            raise WorkflowException, 'Object is in an undefined state'
++        if method_id not in sdef.transitions:
++            raise Unauthorized(method_id)
++        tdef = self.transitions.get(method_id, None)
++        if tdef is None or tdef.trigger_type != TRIGGER_WORKFLOW_METHOD:
++            raise WorkflowException, (
++                'Transition %s is not triggered by a workflow method'
++                % method_id)
++        if not self._checkTransitionGuard(tdef, ob):
++            raise Unauthorized(method_id)
++        res = func(*args, **kw)
++        try:
++            self._changeStateOf(ob, tdef)
++        except ObjectDeleted:
++            # Re-raise with a different result.
++            raise ObjectDeleted(res)
++        except ObjectMoved, ex:
++            # Re-raise with a different result.
++            raise ObjectMoved(ex.getNewObject(), res)
++        return res
++
+     security.declarePrivate('isInfoSupported')
+     def isInfoSupported(self, ob, name):
+         '''
+diff -uNr Products.DCWorkflow-2.2.4/Products/DCWorkflow/dtml/transition_properties.dtml Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/dtml/transition_properties.dtml
+--- Products.DCWorkflow-2.2.4/Products/DCWorkflow/dtml/transition_properties.dtml	2011-11-01 18:55:01.000000000 +0100
++++ Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/dtml/transition_properties.dtml	2013-10-31 16:42:05.021141352 +0100
+@@ -56,6 +56,16 @@
+ </tr>
+ 
+ <tr>
++<th></th>
++<td>
++<dtml-let checked="trigger_type==2 and 'checked' or ' '">
++<input type="radio" name="trigger_type" value="2" &dtml-checked; />
++Initiated by WorkflowMethod
++</dtml-let>
++</td>
++</tr>
++
++<tr>
+ <th align="left">Script (before)</th>
+ <td>
+ <select name="script_name">
+diff -uNr Products.DCWorkflow-2.2.4/Products/DCWorkflow/dtml/transitions.dtml Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/dtml/transitions.dtml
+--- Products.DCWorkflow-2.2.4/Products/DCWorkflow/dtml/transitions.dtml	2011-11-01 18:55:01.000000000 +0100
++++ Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/dtml/transitions.dtml	2013-10-31 16:42:05.021141352 +0100
+@@ -17,7 +17,8 @@
+   <td>
+    Destination state: <code><dtml-if new_state_id>&dtml-new_state_id;<dtml-else>(Remain in state)</dtml-if></code> <br />
+    Trigger: <dtml-var expr="(trigger_type == 0 and 'Automatic') or
+-                            (trigger_type == 1 and 'User action')">
++                            (trigger_type == 1 and 'User action') or
++                            (trigger_type == 2 and 'WorkflowMethod')">
+    <br />
+    <dtml-if script_name>
+      Script (before): &dtml-script_name;
+diff -uNr Products.DCWorkflow-2.2.4/Products/DCWorkflow/exportimport.py Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/exportimport.py
+--- Products.DCWorkflow-2.2.4/Products/DCWorkflow/exportimport.py	2011-11-01 18:55:01.000000000 +0100
++++ Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/exportimport.py	2013-10-31 16:42:09.221141578 +0100
+@@ -34,7 +34,7 @@
+ from Products.GenericSetup.interfaces import ISetupEnviron
+ from Products.GenericSetup.utils import BodyAdapterBase
+ 
+-TRIGGER_TYPES = ( 'AUTOMATIC', 'USER' )
++TRIGGER_TYPES = ( 'AUTOMATIC', 'USER', 'METHOD' )
+ _FILENAME = 'workflows.xml'
+ 
+ 
+diff -uNr Products.DCWorkflow-2.2.4/Products/DCWorkflow/Transitions.py Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/Transitions.py
+--- Products.DCWorkflow-2.2.4/Products/DCWorkflow/Transitions.py	2011-11-01 18:55:01.000000000 +0100
++++ Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/Transitions.py	2013-10-31 16:42:12.389141749 +0100
+@@ -31,6 +31,7 @@
+ 
+ TRIGGER_AUTOMATIC = 0
+ TRIGGER_USER_ACTION = 1
++TRIGGER_WORKFLOW_METHOD = 2
+ 
+ 
+ class TransitionDefinition (SimpleItem):
diff --git a/stack/erp5/buildout.cfg b/stack/erp5/buildout.cfg
index 261cbda13..29ca07116 100644
--- a/stack/erp5/buildout.cfg
+++ b/stack/erp5/buildout.cfg
@@ -417,7 +417,16 @@ initialization =
 
 [patched-eggs]
 recipe = minitage.recipe.egg
-eggs = ZODB3
+eggs =
+  Acquisition
+  Products.DCWorkflow
+  ZODB3
+Acquisition-patches = ${:_profile_base_location_}/../../component/egg-patch/Acquisition/aq_dynamic.patch
+Acquisition-patch-options = -p1
+Acquisition-patch-binary = ${patch:location}/bin/patch
+Products.DCWorkflow-patches = ${:_profile_base_location_}/../../component/egg-patch/Products.DCWorkflow/workflow_method.patch
+Products.DCWorkflow-patch-options = -p1
+Products.DCWorkflow-patch-binary = ${patch:location}/bin/patch
 ZODB3-patches = ${:_profile_base_location_}/../../component/egg-patch/ZODB3-3.10.5.patch
 ZODB3-patch-options = -p1
 ZODB3-patch-binary = ${patch:location}/bin/patch
@@ -570,9 +579,10 @@ scripts =
   zodbpack
 
 [versions]
-# pin Acquisition and Products.DCWorkflow to Nexedi flavour of eggs
-Acquisition = 2.13.8nxd001
-Products.DCWorkflow = 2.2.4nxd001
+# patched eggs
+Acquisition = 2.13.8-ZMinitagePatched-AqDynamic
+Products.DCWorkflow = 2.2.4-ZMinitagePatched-WorkflowMethod
+ZODB3 = 3.10.5-ZMinitagePatched-ZODB33105
 
 # specify dev version to be sure that an old released version is not used
 cloudooo = 1.2.5-dev
@@ -625,9 +635,6 @@ Products.CMFDefault = 2.2.3
 Products.CMFTopic = 2.2.1
 Products.CMFUid = 2.2.1
 
-# patched eggs
-ZODB3 = 3.10.5-ZMinitagePatched-ZODB33105
-
 # newer version requires zope.traversing>=4.0.0a2.
 zope.app.appsetup = 3.16.0
 
-- 
2.30.9