diff --git a/product/ERP5Type/Core/CacheFactory.py b/product/ERP5Type/Core/CacheFactory.py
new file mode 100644
index 0000000000000000000000000000000000000000..78f6d44e2ea0b760a8d32582fcddf14931150ce9
--- /dev/null
+++ b/product/ERP5Type/Core/CacheFactory.py
@@ -0,0 +1,88 @@
+##############################################################################
+#
+# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
+#                     Ivan Tyagov <ivan@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.
+#
+##############################################################################
+
+
+from AccessControl import ClassSecurityInfo
+from Products.CMFCore import CMFCorePermissions
+from Products.ERP5Type import Permissions
+from Products.ERP5Type import PropertySheet
+from Products.ERP5Type.PropertySheet.CacheFactory import CacheFactory
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Type.Cache import CachingMethod
+
+class CacheFactory(XMLObject):
+  """
+  CacheFactory is a collection of cache plugins. CacheFactory is an object which lives in ZODB.
+  """
+    
+  meta_type = 'ERP5 Cache Factory'
+  portal_type = 'Cache Factory'
+  isPortalContent = 1 
+  isRADContent = 1
+  
+  allowed_types = ('ERP5 Ram Cache', 
+                   'ERP5 Distributed Ram Cache', 
+                   'ERP5 SQL Cache',
+                  )
+    
+  security = ClassSecurityInfo()
+  security.declareProtected(CMFCorePermissions.ManagePortal,
+                            'manage_editProperties',
+                            'manage_changeProperties',
+                            'manage_propertiesForm',
+                            )
+
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.SimpleItem
+                    , PropertySheet.Folder
+                    , CacheFactory  
+                    )
+    
+       
+  def getCachePluginList(self):
+    """ get ordered list of installed cache plugins in ZODB """
+    cache_plugins = self.objectValues(self.allowed_types)
+    cache_plugins = map(None, cache_plugins)
+    cache_plugins.sort(lambda x,y: cmp(x.int_index, y.int_index))
+    return  cache_plugins
+    
+  security.declareProtected(Permissions.AccessContentsInformation, 'getRamCacheFactory')
+  def getRamCacheFactory(self):
+    """ Return RAM based cache factory """
+    erp5_site_id = self.getPortalObject().getId()
+    return CachingMethod.factories[erp5_site_id][self.cache_scope]
+    
+  security.declareProtected(Permissions.AccessContentsInformation, 'getRamCacheFactoryPluginList')
+  def getRamCacheFactoryPluginList(self):
+    """ Return RAM based list of cache plugins for this factory """
+    return self.getRamCacheFactory().getCachePluginList()
+  
+  def clearCache(self):
+    """ clear cache for this cache factory """
+    for cp in self.getRamCacheFactory().getCachePluginList():
+      cp.clearCache()
diff --git a/product/ERP5Type/Core/DistributedRamCache.py b/product/ERP5Type/Core/DistributedRamCache.py
new file mode 100644
index 0000000000000000000000000000000000000000..62be7d028d88ba5b32535fd1928253b8ef951053
--- /dev/null
+++ b/product/ERP5Type/Core/DistributedRamCache.py
@@ -0,0 +1,63 @@
+##############################################################################
+#
+# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
+#                     Ivan Tyagov <ivan@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.
+#
+##############################################################################
+
+from AccessControl import ClassSecurityInfo
+from Products.CMFCore import CMFCorePermissions
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Type import PropertySheet
+from Products.ERP5.PropertySheet.SortIndex import SortIndex
+from Products.ERP5Type.PropertySheet.BaseCache import BaseCache
+from Products.ERP5Type.PropertySheet.DistributedRamCache import DistributedRamCache
+
+class DistributedRamCache(XMLObject):
+  """
+  DistributedRamCache is a Zope (persistent) representation of 
+  the Distributed RAM Cache real cache plugin object.
+  """
+  
+  meta_type='ERP5 Distributed Ram Cache'
+  portal_type='Distributed Ram Cache'
+  isPortalContent = 1
+  isRADContent = 1
+  
+  allowed_types = ()
+    
+  security = ClassSecurityInfo()
+  security.declareProtected(CMFCorePermissions.ManagePortal,
+                            'manage_editProperties',
+                            'manage_changeProperties',
+                            'manage_propertiesForm',
+                            )
+
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.SimpleItem
+                    , PropertySheet.Folder
+                    , BaseCache
+                    , SortIndex
+                    , DistributedRamCache 
+                    )
diff --git a/product/ERP5Type/Core/Folder.py b/product/ERP5Type/Core/Folder.py
new file mode 100644
index 0000000000000000000000000000000000000000..c7d1cfd6522bb60aa4f5e87a2b897fb9f6c4b9cd
--- /dev/null
+++ b/product/ERP5Type/Core/Folder.py
@@ -0,0 +1,875 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+from AccessControl import ClassSecurityInfo
+from Acquisition import aq_base, aq_self
+from OFS.History import Historical
+import ExtensionClass
+
+from Products.CMFCore.utils import _getAuthenticatedUser
+from Products.CMFCore.CMFCatalogAware import CMFCatalogAware
+
+from Products.ERP5Type.Base import Base
+from Products.ERP5Type.CopySupport import CopyContainer
+from Products.ERP5Type import PropertySheet, Permissions
+from Products.ERP5Type.XMLExportImport import Folder_asXML
+from Products.ERP5Type.Cache import CachingMethod
+from Products.ERP5Type.Utils import sortValueList
+
+try:
+  from Products.CMFCore.CMFBTreeFolder import CMFBTreeFolder
+except ImportError:
+  from Products.BTreeFolder2.CMFBTreeFolder import CMFBTreeFolder
+from AccessControl import getSecurityManager
+from Products.ERP5Type import Permissions
+from random import randint
+
+import os
+
+from zLOG import LOG, PROBLEM
+
+# Dummy Functions for update / upgrade
+def dummyFilter(object,REQUEST=None):
+  return 1
+
+def dummyTestAfter(object,REQUEST=None):
+  return []
+
+class FolderMixIn(ExtensionClass.Base):
+  """A mixin class for folder operations, add content, delete content etc.
+  """
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  security.declarePublic('newContent')
+  def newContent(self, id=None, portal_type=None, id_group=None,
+          default=None, method=None, immediate_reindex=0,
+          container=None, created_by_builder=0, activate_kw=None,
+          is_indexable=None, temp_object=0, **kw):
+    """Creates a new content.
+    This method is public, since TypeInformation.constructInstance will perform
+    the security check.
+    """
+    if container is None:
+      container = self
+    if id is None:
+      new_id = str(container.generateNewId(id_group = id_group,
+                                           default=default, method=method))
+    else:
+      new_id = str(id)
+    if portal_type is None:
+      # XXX This feature is very confusing 
+      # And made the code more difficult to update
+      portal_type = container.allowedContentTypes()[0].id
+
+    if temp_object:
+      from Products.ERP5Type import Document
+      # we get an object from factory only for first temp container object
+      # otherwise we get an id so we can use the classic way
+      if not hasattr(container, 'isTempObject') or \
+             (hasattr(container, 'isTempObject') and not container.isTempObject()):
+        factory_name = 'newTemp%s' %(portal_type.replace(' ', ''))
+        m = getattr(Document, factory_name)
+        return m(container, new_id)
+
+    self.portal_types.constructContent(type_name=portal_type,
+                                       container=container,
+                                       id=new_id,
+                                       created_by_builder=created_by_builder,
+                                       activate_kw=activate_kw,
+                                       is_indexable=is_indexable
+                                       ) # **kw) removed due to CMF bug
+    # TODO :the **kw makes it impossible to create content not based on
+    # ERP5TypeInformation, because factory method often do not support
+    # keywords arguments.
+    
+    new_instance = container[new_id]
+    if kw != {} : new_instance._edit(force_update=1, **kw)
+    if immediate_reindex: new_instance.immediateReindexObject()
+    return new_instance
+  
+  security.declareProtected(
+            Permissions.DeletePortalContent, 'deleteContent')
+  def deleteContent(self, id):
+    """ delete items in this folder.
+      `id` can be a list or a string.
+    """
+    if isinstance(id, str):
+      self._delObject(id)
+    elif isinstance(id, list) or isinstance(id, tuple):
+      for my_id in id:
+        self._delObject(my_id)
+    else:
+      raise TypeError, 'deleteContent only accepts string or list, '\
+                       'not %s' % type(id)
+
+  def _generateRandomId(self):
+    """
+      Generate a random Id.
+      10000 factor makes the odd to generate an already existing Id of 1 out
+      of 10000, not depending on the number of objects present in this folder.
+      len(self)+1 to make sure generation works on an empty Folder.
+    """
+    return '%X' % (randint(1, 10000 * (len(self) + 1)), )
+ 
+  def _generateNextId(self):
+    """
+      Get the last generated Id, increment it until no object with generated
+      Id exist, then save the Id.
+    """
+    try:
+      my_id = int(self.getLastId())
+    except TypeError:
+      my_id = 1
+    while self.hasContent(str(my_id)):
+      my_id = my_id + 1
+    my_id = str(my_id)
+    self._setLastId(my_id) # Make sure no reindexing happens
+    return my_id
+
+  # Automatic ID Generation method
+  security.declareProtected(Permissions.View, 'generateNewId')
+  def generateNewId(self,id_group=None,default=None,method=None):
+    """
+      Generate a new Id which has not been taken yet in this folder.
+      Eventually increment the id number until an available id
+      can be found
+
+      Permission is view because we may want to add content to a folder
+      without changing the folder content itself.
+      XXX
+    """
+    my_id = None
+    if id_group is None:
+      id_group = self.getIdGroup()
+    if id_group in (None, 'None'):
+      id_generator = self.getIdGenerator()
+      if not isinstance(id_generator, str):
+        LOG('Folder.generateNewId', 0, '%s.id_generator is not a string. Falling back on default behaviour.' % (self.absolute_url(), ))
+        id_generator = ''
+      if id_generator != '': # Custom aq_dynamic function (like the one defined on WebSite objects) can find an object which has no name. So we must recognise the default value of id_generator and force safe fallback in this case.
+        idGenerator = getattr(self, id_generator, None)
+        if idGenerator is None:
+          idGenerator = self._generateNextId
+      else:
+        idGenerator = self._generateNextId
+      my_id = idGenerator()
+      while self.hasContent(my_id):
+        my_id = _generateNextId()
+    else:
+      my_id = str(self.portal_ids.generateNewId(id_group=id_group,default=default,method=method))
+    return my_id
+
+  security.declareProtected(Permissions.View, 'hasContent')
+  def hasContent(self,id):
+    return self.hasObject(id)
+
+  # Get the content
+  security.declareProtected(Permissions.View, 'searchFolder')
+  def searchFolder(self, **kw):
+    """
+      Search the content of a folder by calling
+      the portal_catalog.
+    """
+    if not kw.has_key('parent_uid'): #WHY ????
+      kw['parent_uid'] = self.getUid()
+
+    # Make sure that if we use parent base category
+    # We do not have conflicting parent uid values
+    delete_parent_uid = 0
+    if kw.has_key('selection_domain'):
+      if kw['selection_domain'].asDomainDict().has_key('parent'):
+        delete_parent_uid = 1
+    if kw.has_key('selection_report'):
+      if kw['selection_report'].asDomainDict().has_key('parent'):
+        delete_parent_uid = 1
+    if delete_parent_uid:
+      del kw['parent_uid']
+
+    kw2 = {}
+    # Remove useless matter before calling the
+    # catalog. In particular, consider empty
+    # strings as None values
+    for cname in kw.keys():
+      if kw[cname] != '' and kw[cname] != None:
+        kw2[cname] = kw[cname]
+    # The method to call to search the folder
+    # content has to be called z_search_folder
+    method = self.portal_catalog.portal_catalog
+    return method(**kw2)
+
+  security.declareProtected(Permissions.View, 'countFolder')
+  def countFolder(self, **kw):
+    """
+      Search the content of a folder by calling
+      the portal_catalog.
+    """
+    if not kw.has_key('parent_uid'): #WHY ????
+      kw['parent_uid'] = self.getUid()
+
+    # Make sure that if we use parent base category
+    # We do not have conflicting parent uid values
+    delete_parent_uid = 0
+    if kw.has_key('selection_domain'):
+      if kw['selection_domain'].asDomainDict().has_key('parent'):
+        delete_parent_uid = 1
+    if kw.has_key('selection_report'):
+      if kw['selection_report'].asDomainDict().has_key('parent'):
+        delete_parent_uid = 1
+    if delete_parent_uid:
+      del kw['parent_uid']
+
+    kw2 = {}
+    # Remove useless matter before calling the
+    # catalog. In particular, consider empty
+    # strings as None values
+    for cname in kw.keys():
+      if kw[cname] != '' and kw[cname]!=None:
+        kw2[cname] = kw[cname]
+    # The method to call to search the folder
+    # content has to be called z_search_folder
+    method = self.portal_catalog.countResults
+    return method(**kw2)
+
+  # Count objects in the folder
+  security.declarePrivate('_count')
+  def _count(self, **kw):
+    """
+      Returns the number of items in the folder.
+    """
+    return self.countFolder(**kw)[0][0]
+
+class Folder( CopyContainer, CMFBTreeFolder, Base, FolderMixIn):
+  """
+  A Folder is a subclass of Base but not of XMLObject.
+  Folders are not considered as documents and are therefore
+  not synchronisable.
+
+  ERP5 folders are implemented as CMFBTreeFolder objects
+  and can store up to a million documents on a standard
+  computer.
+  ERP5 folders will eventually use in the near future the
+  AdaptableStorage implementation in order to reach performances
+  of 10 or 100 millions of documents in a single folder.
+
+  ERP5 folders include an automatic id generation feature
+  which allows user not to define an id when they create
+  a new document in a folder.
+
+  ERP5 folders use the ZSQLCatalog to search for objects
+  or display content. This requires a method called
+  *z_search_folder* to be put inside the ZSQLCatalog object
+  of the ERP5 portal.
+
+  An ERP5 Binder document class will eventually be defined
+  in order to implement a binder of documents which can itself
+  be categorized.
+  """
+
+  meta_type = 'ERP5 Folder'
+  portal_type = 'Folder'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+  
+  manage_options = ( CMFBTreeFolder.manage_options +
+                     Historical.manage_options +
+                     CMFCatalogAware.manage_options
+                   )
+  # Declarative properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.SimpleItem
+                    , PropertySheet.Folder
+                    )
+
+  # Class inheritance fixes
+  security.declareProtected( Permissions.ModifyPortalContent, 'edit' )
+  edit = Base.edit
+  security.declareProtected( Permissions.ModifyPortalContent, '_edit' )
+  _edit = Base._edit
+  _setPropValue = Base._setPropValue
+  _propertyMap = Base._propertyMap # are there any others XXX ?
+
+  # Override Zope default by folder id generation
+  def _get_id(self, id):
+    if self._getOb(id, None) is None :
+      return id
+    return self.generateNewId()
+    
+  #security.declareProtected( Permissions.DeletePortalContent, 'manage_delObjects' )
+  #manage_delObjects = CopyContainer.manage_delObjects
+
+  # Implementation
+#   security.declarePrivate('_setObject')
+#   def _setObject(self, id, object, roles=None, user=None, set_owner=1):
+#     """
+#       This method is here in order to dynamically update old
+#       folders into the new BTree folder type.
+#       This method is destructive in the sens that objects
+#       of the old folder will be lost during the update
+#     """
+#     # First make sur the folder has been initialized
+#     if not hasattr(self, '_tree'):
+#       CMFBTreeFolder.__init__(self, self.id)
+#     if not self._tree:
+#       CMFBTreeFolder.__init__(self, self.id)
+#     # Then insert the object
+#     CMFBTreeFolder._setObject(self, id, object, roles=roles, user=user, set_owner=set_owner)
+# This method destroys the title when we create new object in empty folder
+
+  security.declareProtected(Permissions.View, 'hasContent')
+  def hasContent(self,id):
+    return self.hasObject(id)
+
+  security.declareProtected( Permissions.ModifyPortalContent, 'exportAll' )
+  def exportAll(self,dir=None):
+    """
+    Allows to export all object inside a particular folder, one by one
+    """
+    folder_id = self.getId()
+    if dir != None:
+      for id in self.objectIds():
+        f = os.path.join(dir, '%s___%s.zexp' % (folder_id,id))
+        ob = self._getOb(id)
+        ob._p_jar.exportFile(ob._p_oid,f)
+      get_transaction().commit()
+
+  security.declareProtected( Permissions.ModifyPortalContent, 'recursiveApply')
+  def recursiveApply(self, filter=dummyFilter, method=None,
+                    test_after=dummyTestAfter, include=1, REQUEST=None, **kw):
+    """
+      Apply a method to self and to all children
+
+      filter      --    only instances which return 1 when applied filter
+                        are considered
+
+      method      --    the method to apply to acceptable instances
+
+      test_after  --    test to apply after calling method in order to search
+                        for inconsistencies
+
+      include     --    if set to 1 (default), apply method to self
+
+
+      REQUEST     --    the http REQUEST (if needed)
+
+      **kw        --    optional parameters passed to method
+    """
+    update_list = []
+    #LOG('Folder, recursiveApply ',0,"first one self.path: %s" % self.getPath())
+
+    # Only apply method to self if filter is to 1 and filter returns 1
+    if include==1 and filter(object=self.getObject(),REQUEST=REQUEST):
+      method_message = method(object=self.getObject(),REQUEST=REQUEST, **kw)
+      if type(method_message) is type([]):
+        update_list += method_message
+      update_list += test_after(object=self.getObject(),REQUEST=REQUEST)
+
+    for o in self.objectValues(): # contentValues sometimes fail in BTreeFolder
+      # Test on each sub object if method should be applied
+      if filter(object=o,REQUEST=REQUEST):
+        method_message = method(object=o,REQUEST=REQUEST, **kw)
+        if type(method_message) is type([]):
+          update_list += method_message
+        update_list += test_after(o,REQUEST=REQUEST)
+      # And commit subtransaction
+      #get_transaction().commit(1)
+      get_transaction().commit() # we may use commit(1) some day XXX
+      # Recursively call recursiveApply if o has a recursiveApply method (not acquired)
+      obase = aq_base(o)
+      if hasattr(obase, 'recursiveApply'):
+        #LOG('Found recursiveApply', 0, o.absolute_url())
+        update_list += o.recursiveApply(filter=filter, \
+                              method=method, test_after=test_after,REQUEST=REQUEST,include=0,**kw)
+
+    return update_list
+
+  security.declareProtected( Permissions.ModifyPortalContent, 'updateAll' )
+  def updateAll(self, filter=None, method=None, test_after=None, request=None, include=1,**kw):
+    """
+    update all objects inside this particular folder wich
+    returns not None to the test.
+
+    filter have to be a method with one parameter (the object)
+    wich returns None if we must not update the object
+
+    test_after have to be a method with one parameter (the object)
+    wich returns a string
+
+    method is the update method with also one parameter
+
+    """
+    update_list = []
+    #LOG('Folder, updateAll ',0,"first one self.path: %s" % self.getPath())
+
+    if include==1 and filter(object=self.getObject(),request=request):
+      method_message = method(object=self.getObject(),request=request)
+      if type(method_message) is type([]):
+        update_list += method_message
+      update_list += test_after(object=self.getObject(),request=request)
+
+    for o in self.objectValues():
+      # Test if we must apply the upgrade
+      if filter(object=o,request=request):
+        method_message = method(object=o,request=request)
+        if type(method_message) is type([]):
+          update_list += method_message
+        update_list += test_after(object=o,request=request)
+      #for object in o.objectValues():
+        #LOG('Folder, updateAll ',0,"object.id: %s" % object.id)
+      obase = aq_base(o)
+      get_transaction().commit()
+      if hasattr(obase, 'updateAll'):
+        update_list += o.updateAll(filter=filter, \
+                              method=method, test_after=test_after,request=request,include=0,**kw)
+
+    return update_list
+
+  security.declareProtected( Permissions.ModifyPortalContent, 'upgradeObjectClass' )
+  def upgradeObjectClass(self, test_before=None, from_class=None,\
+                         to_class=None, test_after=None):
+    """
+    upgrade the class of all objects inside this
+    particular folder
+     test have to be a method with one parameter
+     migrations is a dictionnary of class, { from_class : to_class }
+    """
+    #LOG("upradeObjectClass: folder ",0,self.id)
+    test_list = []
+    folder = self.getObject()
+    for o in self.listFolderContents():
+      # Make sure this sub object is not the same as object
+      if o.getPhysicalPath() != self.getPhysicalPath():
+        id = o.getId()
+        obase = aq_base(o)
+        # Check if the subobject have to also be upgraded
+        if hasattr(obase,'upgradeObjectClass'):
+          test_list += o.upgradeObjectClass(test_before=test_before, \
+                          from_class=from_class, to_class=to_class,
+                          test_after=test_after)
+        # Test if we must apply the upgrade
+        if test_before(o) is not None:
+          LOG("upradeObjectClass: id ",0,id)
+          klass = obase.__class__
+          LOG("upradeObjectClass: klass ",0,str(klass))
+          LOG("upradeObjectClass: from_class ",0,str(from_class))
+          if klass == from_class:
+            try:
+                newob = to_class(obase.id)
+                newob.id = obase.id # This line activates obase.
+            except AttributeError:
+                newob = to_class(id)
+                newob.id = id
+            keys = obase.__dict__.keys()
+            for k in keys:
+              if k not in ('id', 'meta_type', '__class__'):
+                setattr(newob,k,obase.__dict__[k])
+
+            self.manage_delObjects(id)
+            LOG("upradeObjectClass: ",0,"add new object: %s" % str(newob.id))
+            get_transaction().commit() # XXX this commit should be after _setObject
+            LOG("upradeObjectClass: ",0,"newob.__class__: %s" % str(newob.__class__))
+            self._setObject(id, newob)
+            object_to_test = self._getOb(id)
+            test_list += test_after(object_to_test)
+
+    return test_list
+
+
+  # Catalog related
+  security.declarePublic( 'reindexObject' )
+  def reindexObject(self, *args, **kw):
+    """
+      Fixes the hierarchy structure (use of Base class)
+      XXXXXXXXXXXXXXXXXXXXXXXX
+      BUG here : when creating a new base category
+    """
+    return Base.reindexObject(self, *args, **kw)
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'reindexObjectSecurity')
+  def reindexObjectSecurity(self):
+    """
+        Reindex security-related indexes on the object
+        (and its descendants).
+    """
+    # In ERP5, simply reindex all objects.
+    self.recursiveReindexObject()
+
+  security.declarePublic( 'recursiveReindexObject' )
+  def recursiveReindexObject(self, *args, **kw):
+    """
+      Fixes the hierarchy structure (use of Base class)
+      XXXXXXXXXXXXXXXXXXXXXXXX
+      BUG here : when creating a new base category
+    """
+    if self.isIndexable:
+      self.activate(group_method_id='portal_catalog/catalogObjectList', expand_method_id='getIndexableChildValueList', alternate_method_id='alternateReindexObject', **kw).recursiveImmediateReindexObject(*args, **kw)
+
+  security.declareProtected( Permissions.AccessContentsInformation, 'getIndexableChildValueList' )
+  def getIndexableChildValueList(self):
+    """
+      Get indexable childen recursively.
+    """
+    value_list = []
+    if self.isIndexable:
+      value_list.append(self)
+      for c in self.objectValues():
+        if hasattr(aq_base(c), 'getIndexableChildValueList'):
+          value_list.extend(c.getIndexableChildValueList())
+    return value_list
+
+  security.declarePublic( 'recursiveImmediateReindexObject' )
+  def recursiveImmediateReindexObject(self, *args, **kw):
+      """
+        Applies immediateReindexObject recursively
+      """
+      # Reindex self
+      root_indexable = int(getattr(self.getPortalObject(),'isIndexable',1))
+      if self.isIndexable and root_indexable:
+        self.flushActivity(invoke = 0, method_id='immediateReindexObject') # This might create a recursive lock
+        self.flushActivity(invoke = 0, method_id='recursiveImmediateReindexObject') # This might create a recursive lock
+        self.immediateReindexObject(*args, **kw)
+      # Reindex contents
+      #LOG('recursiveImmediateReindexObject', 0, 'self = %r, self.objectValues = %r' % (self, self.objectValues()))
+      for c in self.objectValues():
+        if hasattr(aq_base(c), 'recursiveImmediateReindexObject'):
+          c.recursiveImmediateReindexObject(*args, **kw)
+
+  security.declareProtected( Permissions.ModifyPortalContent, 'recursiveMoveObject' )
+  def recursiveMoveObject(self):
+    """
+      Called when the base of a hierarchy is renamed
+    """
+    # Reindex self
+    if self.isIndexable:
+      self.moveObject()
+    # Reindex contents
+    for c in self.objectValues():
+      if hasattr(aq_base(c), 'recursiveMoveObject'):
+        c.recursiveMoveObject()
+
+  # Special Relation keyword : 'content' and 'container'
+  security.declareProtected( Permissions.AccessContentsInformation, '_getCategoryMembershipList' )
+  def _getCategoryMembershipList(self, category,
+                          spec=(), filter=None, portal_type=(), base=0 ):
+    if category == 'content':
+      content_list = self.searchFolder(portal_type=spec)
+      return map(lambda x: x.relative_url, content_list)
+    else:
+      return Base.getCategoryMembershipList(self, category,
+          spec=spec, filter=filter, portal_type=portal_type,  base=base)
+
+  # Alias - class inheritance resolution
+  security.declareProtected( Permissions.View, 'Title' )
+  Title = Base.Title
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'checkConsistency')
+  def checkConsistency(self, fixit=0):
+    """
+    Check the consistency of this object, then
+    check recursively the consistency of every sub object.
+    """
+    error_list = []
+    # Fix BTree
+    if fixit:
+      btree_ok = self._cleanup()
+      if not btree_ok:
+        # We must commit if we want to keep on recursing
+        get_transaction().commit(1)
+        error_list += [(self.getRelativeUrl(), 'BTree Inconsistency',
+                       199, '(fixed)')]
+    # Call superclass
+    error_list += Base.checkConsistency(self, fixit=fixit)
+    # We must commit before listing folder contents
+    # in case we erased some data
+    if fixit:
+      get_transaction().commit(1)
+    # Then check the consistency on all sub objects
+    for obj in self.contentValues():
+      if fixit:
+        extra_errors = obj.fixConsistency()
+      else:
+        extra_errors = obj.checkConsistency()
+      if len(extra_errors) > 0:
+        error_list += extra_errors
+    # We should also return an error if any
+    return error_list
+
+  security.declareProtected( Permissions.AccessContentsInformation, 'asXML' )
+  def asXML(self, ident=0):
+    """
+        Generate an xml text corresponding to the content of this object
+    """
+    return Folder_asXML(self,ident=ident)
+
+  # Optimized Menu System
+  security.declarePublic('getVisibleAllowedContentTypeList')
+  def getVisibleAllowedContentTypeList(self):
+    """
+      List portal_types' names wich can be added in this folder / object.
+      Cache results.
+
+      This function is *much* similar to allowedContentTypes, except it does
+      not returns portal types but their ids and filter out those listed as
+      hidden content types. It allows to be much faster when only the type id
+      is needed.
+    """
+    if not getSecurityManager().checkPermission(
+                      Permissions.AddPortalContent, self):
+      return []
+
+    portal = self.getPortalObject()
+
+    def _getVisibleAllowedContentTypeList():
+      hidden_type_list = portal.portal_types.getTypeInfo(self).getHiddenContentTypeList()
+      return [type.id for type in CMFBTreeFolder.allowedContentTypes(self) if type.id not in hidden_type_list]
+
+    user = str(_getAuthenticatedUser(self))
+    portal_type = self.getPortalType()
+    portal_path = portal.getPhysicalPath()
+
+    _getVisibleAllowedContentTypeList = CachingMethod(_getVisibleAllowedContentTypeList,
+                                                      id=("_getAllowedContentTypeTitleList",
+                                                          user, portal_path, portal_type),
+                                                      cache_duration=None)
+    return _getVisibleAllowedContentTypeList()
+  
+  security.declarePublic('allowedContentTypes')
+  def allowedContentTypes( self ):
+    """ List portal_types which can be added in this folder / object.
+        Cache results.
+        Only paths are cached, because we must not cache objects.
+        This makes the result, even if based on cache, O(n) so it becomes quite 
+        costly with many allowed content types.
+        Example:
+         on Person (12 allowed content types): 1000 calls take 3s.
+         on Person Module (1 allowed content type): 1000 calls take 0.3s.
+    """
+    # if we don't have add portal content permission, return directly.
+    # this prevents returning cached allowed types when the user no longer have
+    # the permission to any content type. (security definitions in workflows
+    # usually remove some permission once an object is "Valid")
+    # This also prevents filling the cache with an empty list, when the user
+    # does not have the permission to add any content yet.
+
+    # XXX this works just fine, unless some objects can be added with another
+    # permission that "Add portal content". For now, this is only the case for
+    # Role Definition objects, but this shows that generally speaking, this is
+    # not the right approach.
+    if not getSecurityManager().checkPermission(
+                      Permissions.AddPortalContent, self):
+      return []
+    
+    def _allowedContentTypes( portal_type=None, user=None, portal_path=None ):
+      # Sort the list for convenience -yo
+      # XXX This is not the best solution, because this does not take
+      # account i18n into consideration.
+      # XXX So sorting should be done in skins, after translation is performed.
+      def compareTypes(a, b): return cmp(a.title or a.id, b.title or b.id)
+      type_list = CMFBTreeFolder.allowedContentTypes(self)
+      type_list.sort(compareTypes)
+      return ['/'.join(x.getPhysicalPath()) for x in type_list]
+    
+    _allowedContentTypes = CachingMethod( _allowedContentTypes,
+                                          id = 'allowedContentTypes',
+                                          cache_duration = None)
+    user = str(_getAuthenticatedUser(self))
+    portal_type = self.getPortalType()
+    portal = self.getPortalObject()
+    portal_path = portal.getPhysicalPath()
+    return [portal.restrictedTraverse(path) for path in
+              _allowedContentTypes( portal_type = portal_type,
+                                    user = user,
+                                    portal_path = portal_path )]
+
+  # Multiple Inheritance Priority Resolution
+  _setProperty = Base._setProperty
+  setProperty = Base.setProperty
+  getProperty = Base.getProperty
+  hasProperty = Base.hasProperty
+  view = Base.view
+
+  # Aliases
+  getObjectIds = CMFBTreeFolder.objectIds
+
+  # Overloading
+  security.declareProtected( Permissions.AccessContentsInformation, 'getParentSqlExpression' )
+  def getParentSqlExpression(self, table = 'catalog', strict_membership = 0):
+    """
+      Builds an SQL expression to search children and subclidren
+    """
+    if strict_membership:
+      return Base.getParentSqlExpression(self, table=table, strict_membership=strict_membership)
+    result = "%s.parent_uid = %s" % (table, self.getUid())
+    for o in self.objectValues():
+      if hasattr(aq_base(o), 'objectValues'):
+        # Do not consider non folder objects
+        result = "%s OR %s" % (result, o.getParentSqlExpression(table=table, strict_membership=strict_membership))
+    return "( %s )" % result
+
+
+  def mergeContent(self,from_object=None,to_object=None, delete=1,**kw):
+    """
+    This method will merge two objects.
+
+    When we have to different objects wich represent the same content, we
+    may want to merge them. In this case, we want to be sure to report
+
+    """
+    if from_object is None or to_object is None:
+      return
+
+    from_object_related_object_list = self.portal_categories.getRelatedValueList(from_object)
+    to_object_url = to_object.getRelativeUrl()
+    from_object_url = from_object.getRelativeUrl()
+    corrected_list = []
+    for object in from_object_related_object_list:
+      #LOG('Folder.mergeContent, working on object:',0,object)
+      object_url = object.getRelativeUrl()
+      new_category_list = []
+      found = 0
+      for category in object.getCategoryList(): # so ('destination/person/1',...)
+        #LOG('Folder.mergeContent, working on category:',0,category)
+        linked_object_url = '/'.join(category.split('/')[1:])
+        if linked_object_url == from_object_url:
+          base_category = category.split('/')[0]
+          found = 1
+          new_category_list.append(base_category + '/' + to_object_url)
+        else:
+          new_category_list.append(category)
+      if found:
+        corrected_list.append(object)
+        object.setCategoryList(new_category_list)
+        object.immediateReindexObject()
+    if delete:
+      if len(from_object.portal_categories.getRelatedValueList(from_object))==0:
+        parent = from_object.getParentValue()
+        parent.manage_delObjects(from_object.getId())
+    return corrected_list
+
+  security.declareProtected( Permissions.AccessContentsInformation,
+                             'objectValues' )
+  def objectValues(self, spec=None, meta_type=None, portal_type=None,
+                   sort_on=None, sort_order=None, **kw):
+    """
+    Returns a list containing object contained in this folder.
+    """
+    if meta_type is not None:
+      spec = meta_type
+    # when an object inherits from Folder after it was instanciated, it lacks
+    # its BTreeFolder properties.
+    if getattr(self, '_tree', None) is None:
+      try:
+        self._initBTrees()
+      except AttributeError:
+        from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2Base
+        BTreeFolder2Base.__init__(self, self.getId())
+    object_list = CMFBTreeFolder.objectValues(self, spec=spec)
+    if portal_type is not None:
+      if type(portal_type) == type(''):
+        portal_type = (portal_type,)
+      object_list = filter(lambda x: x.getPortalType() in portal_type, object_list)
+    object_list = sortValueList(object_list, sort_on, sort_order, **kw)
+    return object_list
+
+  security.declareProtected( Permissions.AccessContentsInformation,
+                             'contentValues' )
+  def contentValues(self, spec=None, meta_type=None, portal_type=None,
+                    sort_on=None, sort_order=None, **kw):
+    """
+    Returns a list containing object contained in this folder.
+    Filter objects with appropriate permissions (as in contentValues)
+    """
+    if meta_type is not None:
+      spec = meta_type
+    if portal_type is not None:
+      kw['portal_type'] = portal_type
+    filter = kw.pop('filter', {}) or {}
+    kw.update(filter)
+    object_list = CMFBTreeFolder.contentValues(self, spec=spec, filter=kw)
+    object_list = sortValueList(object_list, sort_on, sort_order, **kw)
+    return object_list
+
+  # Override security declaration of CMFCore/PortalFolder (used by CMFBTreeFolder)
+  security.declareProtected(Permissions.ModifyPortalContent,'setDescription')
+  security.declareProtected( Permissions.ModifyPortalContent, 'setTitle' )
+
+  security.declareProtected( Permissions.AccessContentsInformation, 'manage_copyObjects' ) # XXX Why this one doesn't work in CopySupport ?
+  security.declareProtected( Permissions.AddPortalContent, 'manage_pasteObjects' ) # XXX Why this one doesn't work in CopySupport ?
+
+  # Template Management
+  security.declareProtected(Permissions.View, 'getDocumentTemplateList')
+  def getDocumentTemplateList(self) :
+    """
+      Returns the list of allowed templates for this folder
+      by calling the preference tool
+    """
+    return self.getPortalObject().portal_preferences.getDocumentTemplateList(self)
+  
+  security.declareProtected(Permissions.ModifyPortalContent,'makeTemplate')
+  def makeTemplate(self):
+    """
+      Make document behave as a template.
+      A template is no longer indexable
+
+      TODO:
+         - prevent from changing templates or invoking workflows
+    """
+    Base.makeTemplate(self)
+    for o in self.objectValues():
+      if hasattr(aq_base(o), 'makeTemplate'): o.makeTemplate()
+
+  security.declareProtected(Permissions.ModifyPortalContent,'makeTemplateInstance')
+  def makeTemplateInstance(self):
+    """
+      Make document behave as standard document (indexable)
+    """
+    Base.makeTemplateInstance(self)
+    for o in self.objectValues():
+      if hasattr(aq_base(o), 'makeTemplateInstance'): o.makeTemplateInstance()
+  
+  def _delObject(self, id, dp=1):
+    """
+      _delObject is redefined here in order to make sure
+      we do not do silent except while we remove objects
+      from catalog
+    """
+    object = self._getOb(id)
+    object.manage_beforeDelete(object, self)
+    self._delOb(id)
+
+# Overwrite Zope setTitle()
+Folder.setTitle = Base.setTitle
diff --git a/product/ERP5Type/Core/RamCache.py b/product/ERP5Type/Core/RamCache.py
new file mode 100644
index 0000000000000000000000000000000000000000..44c9d0c478d62bddae82a2a020a559f04ae87163
--- /dev/null
+++ b/product/ERP5Type/Core/RamCache.py
@@ -0,0 +1,60 @@
+##############################################################################
+#
+# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
+#                     Ivan Tyagov <ivan@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.
+#
+##############################################################################
+
+
+from AccessControl import ClassSecurityInfo
+from Products.CMFCore import CMFCorePermissions
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Type import PropertySheet
+from Products.ERP5.PropertySheet.SortIndex import SortIndex
+from Products.ERP5Type.PropertySheet.BaseCache import BaseCache
+
+class RamCache(XMLObject):
+  """
+  RamCache is a Zope (persistent) representation of 
+  the RAM based real cache plugin object.
+  """
+  meta_type = 'ERP5 Ram Cache'
+  portal_type = 'Ram Cache'
+  isPortalContent = 1
+  isRADContent = 1
+  allowed_types = ()
+    
+  security = ClassSecurityInfo()
+  security.declareProtected(CMFCorePermissions.ManagePortal,
+                            'manage_editProperties',
+                            'manage_changeProperties',
+                            'manage_propertiesForm',
+                            )
+
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.SimpleItem
+                    , PropertySheet.Folder
+                    , SortIndex
+                    , BaseCache 
+                    )
diff --git a/product/ERP5Type/Core/SQLCache.py b/product/ERP5Type/Core/SQLCache.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ac49beb10733e0b81c15ad93e6677e10a7e372e
--- /dev/null
+++ b/product/ERP5Type/Core/SQLCache.py
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
+#                     Ivan Tyagov <ivan@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.
+#
+##############################################################################
+
+from AccessControl import ClassSecurityInfo
+from Products.CMFCore import CMFCorePermissions
+from Products.ERP5Type.Base import Base
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5Type import PropertySheet
+from Products.ERP5.PropertySheet.SortIndex import SortIndex
+from Products.ERP5Type.PropertySheet.BaseCache import BaseCache
+from Products.ERP5Type.PropertySheet.SQLCache import SQLCache
+
+class SQLCache(XMLObject):
+  """
+  SQLCache is a Zope (persistent) representation of 
+  the RAM based real SQL cache plugin object.
+  """
+  
+  meta_type = 'ERP5 SQL Cache'
+  portal_type = 'SQL Cache'
+  isPortalContent = 1
+  isRADContent = 1
+  
+  allowed_types = ()
+    
+  security = ClassSecurityInfo()
+  security.declareProtected(CMFCorePermissions.ManagePortal,
+                            'manage_editProperties',
+                            'manage_changeProperties',
+                            'manage_propertiesForm',
+                            )
+
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.SimpleItem
+                    , PropertySheet.Folder
+                    , BaseCache
+                    , SortIndex
+                    , SQLCache 
+                    )
diff --git a/product/ERP5Type/Core/__init__.py b/product/ERP5Type/Core/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391