From b3a221d4f239bb6fd98df6b75d7d881b988c8af9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Tue, 22 May 2007 09:51:50 +0000
Subject: [PATCH] This is Kazuhiko's patch to restore 2 level selections (1
 level of mapping per user, then 1 level per selection_name), this also adds
 conflict resolution for Persistent Mapping storage in portal_selections. This
 conflict resolution is just to prevent restarting transactions.

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@14543 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5Form/Selection.py               |   9 ++
 product/ERP5Form/SelectionTool.py           | 106 ++++++++++++++------
 product/ERP5Form/tests/testSelectionTool.py |   7 +-
 3 files changed, 86 insertions(+), 36 deletions(-)

diff --git a/product/ERP5Form/Selection.py b/product/ERP5Form/Selection.py
index a924eaf365..5647b66d34 100644
--- a/product/ERP5Form/Selection.py
+++ b/product/ERP5Form/Selection.py
@@ -176,6 +176,15 @@ class Selection(Acquisition.Implicit, Traversable, Persistent):
                 v = v.encode('ascii')
               setattr(self, k, v)
 
+    def _p_independent(self) :
+      return 1
+
+    def _p_resolveConflict(self, oldState, savedState, newState) :
+      """Selection are edited by listboxs, so many conflicts can happen,
+         this is a workaround, so that no unnecessary transaction is
+         restarted."""
+      return newState
+
     def __call__(self, method = None, context=None, REQUEST=None):
         #LOG("Selection", 0, str((self.__dict__)))
         #LOG("Selection", 0, str(method))
diff --git a/product/ERP5Form/SelectionTool.py b/product/ERP5Form/SelectionTool.py
index 65ab5bfbd1..efd3bbd940 100644
--- a/product/ERP5Form/SelectionTool.py
+++ b/product/ERP5Form/SelectionTool.py
@@ -160,11 +160,7 @@ class SelectionTool( BaseTool, UniqueObject, SimpleItem ):
       """
       if self.isMemcachedUsed():
         return []
-      user_id = self.portal_membership.getAuthenticatedMember().getUserName()
-      if user_id is not None:
-        prefix = '%s-' % user_id
-        return [x.replace(prefix, '', 1) for x in self.getSelectionContainer().keys() if x.startswith(prefix)]
-      return []
+      return self._getSelectionNameListFromContainer()
 
     # backward compatibility
     security.declareProtected(ERP5Permissions.View, 'getSelectionNames')
@@ -182,34 +178,16 @@ class SelectionTool( BaseTool, UniqueObject, SimpleItem ):
         return None
       return selection(context=context)
 
-    def getSelectionContainer(self):
-      """
-        Return the selection container.
-      """
-      if self.isMemcachedUsed():
-        value = getattr(self, '_v_selection_data', None)
-        if value is None:
-          value = self.getPortalObject().portal_memcached.getMemcachedDict(key_prefix='selection_tool')
-          setattr(self, '_v_selection_data', value)
-      else:
-        value = getattr(self, '_selection_data', None)
-        if value is None:
-          value = PersistentMapping()
-          setattr(self, '_selection_data', value)
-      return value
-
     security.declareProtected(ERP5Permissions.View, 'getSelectionFor')
     def getSelectionFor(self, selection_name, REQUEST=None):
       """
         Returns the selection instance for a given selection_name
       """
-      user_id = self.portal_membership.getAuthenticatedMember().getUserName()
-      if user_id is not None:
-        if isinstance(selection_name, (tuple, list)):
-          selection_name = selection_name[0]
-        selection = self.getSelectionContainer().get('%s-%s' % (user_id, selection_name))
-        if selection is not None:
-          return selection.__of__(self)
+      if isinstance(selection_name, (tuple, list)):
+        selection_name = selection_name[0]
+      selection = self._getSelectionFromContainer(selection_name)
+      if selection is not None:
+        return selection.__of__(self)
 
     security.declareProtected(ERP5Permissions.View, 'setSelectionFor')
     def setSelectionFor(self, selection_name, selection_object, REQUEST=None):
@@ -220,10 +198,7 @@ class SelectionTool( BaseTool, UniqueObject, SimpleItem ):
         # Set the name so that this selection itself can get its own name.
         selection_object.edit(name = selection_name)
 
-      user_id = self.portal_membership.getAuthenticatedMember().getUserName()
-      if user_id is not None:
-        self.getSelectionContainer()['%s-%s' % (user_id, selection_name)] = aq_base(selection_object)
-      return
+      self._setSelectionToContainer(selection_name, selection_object)
 
     security.declareProtected(ERP5Permissions.View, 'getSelectionParamsFor')
     def getSelectionParamsFor(self, selection_name, params=None, REQUEST=None):
@@ -1314,8 +1289,73 @@ class SelectionTool( BaseTool, UniqueObject, SimpleItem ):
           return aq_base_name
       return aq_base_name
 
+    def _getUserId(self):
+      return self.portal_membership.getAuthenticatedMember().getUserName()
+
+    def _getSelectionFromContainer(self, selection_name):
+      user_id = self._getUserId()
+      if user_id is None: return None
+      if self.isMemcachedUsed():
+        return self._getMemcachedContainer().get('%s-%s' %
+                                                 (user_id, selection_name))
+      else:
+        return self._getPersistentContainer(user_id).get(selection_name,
+                                                         None)
+
+    def _setSelectionToContainer(self, selection_name, selection):
+      user_id = self._getUserId()
+      if user_id is None: return
+      if self.isMemcachedUsed():
+        self._getMemcachedContainer().set('%s-%s' % (user_id, selection_name), aq_base(selection))
+      else:
+        self._getPersistentContainer(user_id)[selection_name] = aq_base(selection)
+
+    def _getSelectionNameListFromContainer(self):
+      if self.isMemcachedUsed():
+        return []
+      else:
+        user_id = self._getUserId()
+        if user_id is None: return []
+        return self._getPersistentContainer(user_id).keys()
+
+    def _getMemcachedContainer(self):
+      value = getattr(self, '_v_selection_data', None)
+      if value is None:
+        value = self.getPortalObject().portal_memcached.getMemcachedDict(key_prefix='selection_tool')
+        setattr(self, '_v_selection_data', value)
+      return value
+
+    def _getPersistentContainer(self, user_id):
+      if getattr(self, 'selection_data', None) is None:
+        self.selection_data = PersistentMapping()
+      if not self.selection_data.has_key(user_id):
+        self.selection_data[user_id] = SelectionPersistentMapping()
+      return self.selection_data[user_id]
+
 InitializeClass( SelectionTool )
 
+
+class SelectionPersistentMapping(PersistentMapping):
+  """A conflict-free PersistentMapping.
+
+  Like selection objects, the purpose is to only prevent restarting
+  transactions.
+  """
+  def _p_independent(self) :
+    return 1
+
+  def _p_resolveConflict(self, oldState, savedState, newState):
+    # update keys that only savedState has
+    oldState = newState
+    # dict returned by PersistentMapping.__getstate__ contains the data
+    # under _container key, so only compare this key (this is coupled with
+    # PersistentMapping implementation, but this implementation is lot likely
+    # to change, because it would break existing pickles).
+    oldState['_container'].update(savedState['_container'])
+
+    return oldState
+
+
 class TreeListLine:
   def __init__(self,object,is_pure_summary,depth, is_open,select_domain_dict,exception_uid_list):
     self.object=object
@@ -1529,4 +1569,4 @@ def createFolderMixInPageSelectionMethod(listbox_id):
     security_property = getattr(SelectionTool, security_property_id, None)
     if security_property is not None:
       new_security_property_id = '%s__roles__' % (new_property_id, )
-      setattr(FolderMixIn, new_security_property_id, security_property)
\ No newline at end of file
+      setattr(FolderMixIn, new_security_property_id, security_property)
diff --git a/product/ERP5Form/tests/testSelectionTool.py b/product/ERP5Form/tests/testSelectionTool.py
index 1d6252eedb..7ed58ffa85 100644
--- a/product/ERP5Form/tests/testSelectionTool.py
+++ b/product/ERP5Form/tests/testSelectionTool.py
@@ -68,8 +68,9 @@ class TestSelectionTool(ERP5TypeTestCase):
                       self.portal_selections.getSelectionNameList())
     self.assertEquals(['test_selection'],
                       self.portal_selections.getSelectionNames())
-    self.assert_(self.portal_selections.getSelectionContainer() is not None)
-    self.assert_(getattr(self.portal_selections, '_selection_data', None)
+    self.assert_(self.portal_selections._getPersistentContainer('manager')
+                 is not None)
+    self.assert_(getattr(self.portal_selections, 'selection_data', None)
                  is not None)
     # use memcached tool
     self.portal_selections.setStorage('Memcached Tool')
@@ -77,7 +78,7 @@ class TestSelectionTool(ERP5TypeTestCase):
                       self.portal_selections.getSelectionNameList())
     self.assertEquals([],
                       self.portal_selections.getSelectionNames())
-    self.assert_(self.portal_selections.getSelectionContainer() is not None)
+    self.assert_(self.portal_selections._getMemcachedContainer() is not None)
     self.assert_(getattr(self.portal_selections, '_v_selection_data', None)
                  is not None)
 
-- 
2.30.9