Caching
=======
The PluggableAuthService contains a caching mechanism based on the built-in
Zope Caching framework with a mix-in class that defines caching methods for
cacheable content and so-called Cache Managers that store cacheable data.


How does the site admin use it?
-------------------------------
Given a plugin that is cache-enabled the steps to using the cache are
easy. The site administrator needs to...

  - Instantiate a "RAM Cache Manager" inside the PluggableAuthService

  - Using the "Cache" tab in the cache-enabled plugin's ZMI view,
    associate the plugin with the RAM Cache Manager

At this point values will be cached inside the RAM Cache Manager. The
effect can be viewed easily on the "Statistics" tab in the Cache Manager
ZMI view, which shows which plugins have stored values, how many values
are stored, an approximation of the memory consumption for the cached
data, and how often the data has been retrieved from cache.

The Statistics view also provides an easy way to summarily invalidate 
cached values if needed. However, cache invalidation should be handled
by the plugins itself if it is possible.

The PluggableAuthService itself is also cacheable this way. Caching PAS
itself is the easiest way to achieve caching. In PAS, the _findUsers and
_verifyUser methods, being a suitably central spot for caching, have 
been enhanced to use the RAM Cache Manager if so configured.


How does a plugin programmer use it?
------------------------------------
Due to the pluggable (and thus infinitely variable) nature of the 
PluggableAuthService it is up to the plugin developer to decide what to
cache and how to do so. Some of the built-in plugins provide a sample
implementation by caching certain methods' return values and invalidating
these records where necessary. In a nutshell, these are the steps needed
to enable cacheability at the plugin level:

    - Add the Cacheable mix-in class to the list of classes your plugin
      subclasses from
    
    - Determine which method calls should have their return values cached, 
      and which method calls affect the return value of the method that is
      being cached and thus should invalidate the cache
    
    - In the cached method, add code to try and look up the return value in
      the cache first, and only perform the computation if the cache does
      not have the desired data. At the end of computing the return value,
      add it to the cache
    
    - Add cache invalidation to those methods deemed to affect the cached
      method's return value.

A little illustration using code snippets::

  from OFS.Cache import Cacheable
  from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
  from Products.PluggableAuthService.utils import createViewName

  class MyPlugin(BasePlugin, Cacheable):

      def retrieveData(self, key):
          """ Get data for the given key """
          view_name = createViewName('retrieveData', key)
          keywords = {'key': key}
          cached_info = self.ZCacheable_get( view_name=view_name
                                           , keywords=keywords
                                           , default=None
                                           )

          if cached_info is not None:
              return cached_info

          return_value = <perform return value calculation here>

          # Put the computed value into the cache
          self.ZCacheable_set( return_value
                             , view_name=view_name
                             , keywords=keywords
                             )

          return return_value

      def change_data(self, key, new_value):
          """ Change the value for the given key """
          <perform changes here>

          # Also, remove from the cache
          view_name = createViewName('retrieveData', key)
          self.ZCacheable_invalidate(view_name=view_name)

As you can see, due to the variable nature of plugins certain items,
such as the relationships between the different accessor and mutator
methods in use, cannot be computed or guessed, they have to be hardcoded.
That's why inside change_data the view_name "retrieveData" is hardcoded
as the caching key for information returned from the retrieveData method.

This example also shows how, due to the way the built-in Zope caching
framework handles cached data, it is not possible to invalidate specific
entries, such as the value for one specific view_name/key combination. 
All cached records for a specific view_name are invalidated at once.

It must be kept in mind that it is very hard if not impossible to reach
a state where the cache is 100% synchronized with the live data in
those situations where information is retrieved and manipulated in more
than one plugin. Imagine a situation where one plugin's cached answer
is dependent on another plugin that handles updating the underlying data.
The retrieving plugin is not notified of updates happening in the 
"mutator plugin" (and thus does not invalidate cached data) unless the 
plugin developer forces some nasty cross-plugin dependencies.

The PluggableAuthService has two "sample implementations" for plugin
caching. Both the DynamicGroupsPlugin and the ZODBUserManager are 
cache-enabled.

CAVEATS
-------
The Caching mechanism should not be used to cache persistent objects. So
if for some reason your PluggableAuthService emits user objects that are
persistent (which is not the default) you should not enable caching at the
PluggableAuthService-level.

