diff --git a/product/ERP5Type/Cache.py b/product/ERP5Type/Cache.py
index d61f53cbbe534d10f28e2e40eb4cba609d99238b..01b670391c937cbb54a7f9600945731e0ca55dcd 100644
--- a/product/ERP5Type/Cache.py
+++ b/product/ERP5Type/Cache.py
@@ -26,159 +26,208 @@
 #
 ##############################################################################
 
-from Globals import PersistentMapping
-from AccessControl.SecurityInfo import allow_class
+import string
 from time import time
-
+from AccessControl.SecurityInfo import allow_class
+from AccessControl import getSecurityManager
+#from Products import ERP5Cache
 from zLOG import LOG
 
-# XXX need to expire old objects in a way.
-cache_check_time = time()
-CACHE_CHECK_TIMEOUT = 3600
-
-# Special Exception for this code.
-class CachedMethodError(Exception): pass
-
-# This is just a storage.
-class CachedObject:
-  pass
+is_cache_initialized = 0
+
+def initializePortalCachingProperties(self):
+  """ Init CachingMethod properties."""
+  ## check if global CachingMethod is initialized in RAM for this ERP5 site. If not init it
+  global is_cache_initialized
+  if not is_cache_initialized:
+    is_cache_initialized = 1
+    erp5_site_id = self.getPortalObject().getId()
+    ## update cache structure from portal_caches
+    self.getPortalObject().portal_caches.updateCache()
+  
+class CacheFactory:
+  """
+  CacheFactory is a RAM based object which contains different cache plugin objects ordered in a list.
+  """
+  
+  cache_plugins = []
+  cache_duration = 180
+  
+  def __init__(self, cache_plugins, cache_params):
+    self.cache_plugins = cache_plugins
+    self.cache_duration = cache_params.get('cache_duration')
+    
+    ## separete local and shared cache plugins
+    self.quick_cache = self.cache_plugins[0]
+    try:
+      self.shared_caches =self.cache_plugins[1:]  
+    except IndexError:
+      self.shared_caches = []
+    
+    ## set 'check_expire_cache_interval' to the minimal value between
+    ## individual 'check_expire_cache_interval' for each cache plugin contained
+    l = []
+    self._last_cache_expire_check_at = time()
+    for cp in self.cache_plugins:
+      l.append(cp.cache_expire_check_interval)
+    l = filter(lambda x: x!=None and x!=0, l)
+    self.cache_expire_check_interval = min(l)
+    
+  def __call__(self, callable_object, cache_id, scope, cache_duration=None, *args, **kwd):
+    """ 
+    When CacheFactory is called it will try to return cached value using appropriate cache plugin.
+    """
+    cache_duration = self.cache_duration
+    
+    ## Expired Cache (if needed)
+    self.expire()
+
+    quick_cached = self.quick_cache.get(cache_id, scope)
+    if quick_cached:
+      #print "HIT RAM", self.quick_cache
+      return quick_cached.getValue()
+    else:
+      ## not in local, check if it's in shared
+      for shared_cache in self.shared_caches:
+        if shared_cache.has_key(cache_id, scope):
+          cache_entry = shared_cache.get(cache_id, scope)
+          value = cache_entry.getValue()
+          ## update local cache
+          self.quick_cache.set(cache_id, scope, value, cache_entry.cache_duration, cache_entry.calculation_time)
+          return value
+            
+    ## not in any available cache plugins calculate and set to local ..
+    start = time()
+    value = callable_object(*args, **kwd)
+    end = time()
+    calculation_time = end - start
+    self.quick_cache.set(cache_id, scope, value, cache_duration, calculation_time)
+    
+    ## .. and update rest of caches in chain except already updated local one
+    for shared_cache in self.shared_caches:
+      shared_cache.set(cache_id, scope, value, cache_duration, calculation_time)
+    return value
+
+  def expire(self):
+    """ Expire (if needed) cache plugins """
+    now = time()
+    if now > (self._last_cache_expire_check_at + self.cache_expire_check_interval):
+      self._last_cache_expire_check_at = now    
+      for cache_plugin in self.getCachePluginList():
+        cache_plugin.expireOldCacheEntries()
+    
+  def getCachePluginList(self, omit_cache_plugin_name=None):
+    """ get list of all cache plugins except specified by name in omit """ 
+    rl = []
+    for cp in self.cache_plugins:
+      if omit_cache_plugin_name != cp.__class__.__name__:
+        rl.append(cp)
+    return rl
+    
+  def getCachePluginByClassName(self, cache_plugin_name):
+    """ get cache plugin by its class name """
+    for cp in self.cache_plugins:
+      if cache_plugin_name == cp.__class__.__name__:
+        return cp  
+    return None
 
+  def clearCache(self):
+    """ clear cache for this cache factory """
+    for cp in self.cache_plugins:
+      cp.clearCache()
+ 
 class CachingMethod:
   """
-    CachingMethod wraps a callable object to cache the result.
-
-    Example:
-
-      def toto(arg=None):
-        # heavy operations...
-
-      method = CachingMethod(toto, id='toto')
-      return method(arg='titi')
-
-    Some caveats:
-
-      - You must make sure that the method call takes all parameters which can make
-        the result vary. Otherwise, you will get inconsistent results.
-
-      - You should make sure that the method call does not take any parameter which
-        never make the result vary. Otherwise, the cache ratio will be worse.
-
-      - Choose the id carefully. If you use the same id in different methods, this may
-        lead to inconsistent results.
-
-      - This system can be sometimes quite slow if there are many entries, because
-        all entries are checked to expire old ones. This should not be significant,
-        since this is done once per 100 calls.
+  CachingMethod is a RAM based global Zope class which contains different CacheFactory objects
+  for every available ERP5 site instance.
   """
-  # Use this global variable to store cached objects.
-  cached_object_dict = {}
-
-  def __init__(self, callable_object, id = None, cache_duration = 180):
+  
+  ## cache factories will be initialized for every ERP5 site
+  factories = {}
+    
+  ## replace string table for some control characters not allowed in cache id
+  _cache_id_translate_table = string.maketrans("""[]()<>'", """,'__________')
+    
+  def __init__(self, callable_object, id, cache_duration = 180, cache_factory = 'erp5_user_interface'):
     """
-      callable_object must be callable.
-      id is used to identify what call should be treated as the same call.
-      cache_duration is specified in seconds, None to last untill zope process
-        is stopped.
+    callable_object must be callable.
+    id is used to identify what call should be treated as the same call.
+    cache_duration is an old argument kept for backwards compatibility. 
+    cache_duration is specified per cache factory. 
+    cache_factory is the id of the cache_factory to use.
     """
     if not callable(callable_object):
       raise CachedMethodError, "callable_object %s is not callable" % str(callable_object)
     if not id:
       raise CachedMethodError, "id must be specified"
-    self.method = callable_object
     self.id = id
-    self.duration = cache_duration
+    self.callable_object = callable_object
+    self.cache_duration = cache_duration
+    self.cache_factory = cache_factory
 
+    
   def __call__(self, *args, **kwd):
-    """
-      Call the method only if the result is not cached.
-
-      This code looks not aware of multi-threading, but there should no bad effect in reality,
-      since the worst case is that multiple threads compute the same call at a time.
-    """
-    global cache_check_time
-
-    # Store the current time in the REQUEST object, and
-    # check the expiration only at the first time.
-    from Products.ERP5Type.Utils import get_request
-    request = get_request()
-    now = request.get('_erp5_cache_time', None)
-    if now is None:
-      now = time()
-      request.set('_erp5_cache_time', now)
-
-      if cache_check_time + CACHE_CHECK_TIMEOUT < now:
-        # If the time reachs the timeout, expire all old entries.
-        # XXX this can be quite slow, if many results are cached.
-        # LOG('CachingMethod', 0, 'checking all entries to expire')
-        cache_check_time = now
-        try:
-          for index in CachingMethod.cached_object_dict.keys():
-            obj = CachingMethod.cached_object_dict[index]
-            if obj.duration is not None and obj.time + obj.duration < now:
-              # LOG('CachingMethod', 0, 'expire %s' % index)
-              del CachingMethod.cached_object_dict[index]
-        except KeyError:
-          # This is necessary for multi-threading, because two threads can
-          # delete the same entry at a time.
-          pass
-
+    """ Call the method or return cached value using appropriate cache plugin """
+    ## CachingMethod is  global Zope class and thus we must make sure
+    #erp5_site_id = kwd.get('portal_path', ('','erp5'))[1]
+    
+    ## cache scope is based on user which is a kwd argument
+    scope = kwd.get('user', 'GLOBAL')
+    
+    ## generate unique cache id
+    cache_id = self.generateCacheId(self.id, *args, **kwd)
+    
+    try:
+      ## try to get value from cache in a try block 
+      ## which is faster than checking for keys
+      value = self.factories[self.cache_factory](self.callable_object, 
+                                                               cache_id,
+                                                               scope, 
+                                                               self.cache_duration, 
+                                                               *args, 
+                                                               **kwd)
+    except KeyError:
+      ## no caching enabled for this site or no such cache factory
+      value = self.callable_object(*args, **kwd)
+    return value
+    
+  def generateCacheId(self, method_id, *args, **kwd):
+    """ Generate proper cache id based on *args and **kwd  """
+    cache_id = [method_id]    
     key_list = kwd.keys()
     key_list.sort()
-    index = [self.id]
     for arg in args:
-      index.append((None, arg))
+      cache_id.append((None, arg))
     for key in key_list:
-      index.append((key, str(kwd[key])))
-    index = str(index)
-
-    obj = CachingMethod.cached_object_dict.get(index)
-    if obj is None or (obj.duration is not None and obj.time + obj.duration < now):
-      #LOG('CachingMethod', 0, 'cache miss: id = %s, duration = %s, method = %s, args = %s, kwd = %s' % (str(self.id), str(self.duration), str(self.method), str(args), str(kwd)))
-      obj = CachedObject()
-      obj.time = now
-      obj.duration = self.duration
-      obj.result = self.method(*args, **kwd)
-
-      CachingMethod.cached_object_dict[index] = obj
-    else:
-      #LOG('CachingMethod', 0, 'cache hit: id = %s, duration = %s, method = %s, args = %s, kwd = %s' % (str(self.id), str(self.duration), str(self.method), str(args), str(kwd)))
-      pass
-
-    return obj.result
-
+      cache_id.append((key, str(kwd[key])))
+    cache_id = str(cache_id)
+    ## because some cache backends don't allow some chars in cached id we make sure to replace them
+    cache_id = cache_id.translate(self._cache_id_translate_table)
+    return cache_id
+                
 allow_class(CachingMethod)
 
+########################################################
+## Old global cache functions                         ##
+## TODO: Check if it make sense to keep them any more ##
+########################################################
+
 def clearCache(method_id=None):
-  """Clear the cache.
+  """
+  Clear the cache.
   If method_id is specified, it clears the cache only for this method,
-  otherwise, it clears the whole cache."""
-  if method_id is None:
-    CachingMethod.cached_object_dict.clear()
-  else:
-    caching_method_keys = CachingMethod.cached_object_dict.keys()
-    for key in caching_method_keys :
-      # CachingMethod dict contains a string representation of a list
-      # of tuples keys.
-      if method_id in key :
-        del CachingMethod.cached_object_dict[key]
+  otherwise, it clears the whole cache.
+  """
+  pass 
 
-# TransactionCache is a cache per transaction. The purpose of this cache is
-# to accelerate some heavy read-only operations. Note that this must not be
-# enabled when a trasaction may modify ZODB objects.
 def getReadOnlyTransactionCache(context):
-  """Get the transaction cache.
-  """
-  try:
-    return context.REQUEST['_erp5_read_only_transaction_cache']
-  except KeyError:
-    return None
+  """ Get the transaction cache.  """
+  pass
 
 def enableReadOnlyTransactionCache(context):
-  """Enable the transaction cache.
-  """
-  context.REQUEST.set('_erp5_read_only_transaction_cache', {})
+  """ Enable the transaction cache. """
+  pass
 
 def disableReadOnlyTransactionCache(context):
-  """Disable the transaction cache.
-  """
-  context.REQUEST.set('_erp5_read_only_transaction_cache', None)
+  """ Disable the transaction cache. """
+  pass