? includes/cache-default.inc
Index: install.php
===================================================================
RCS file: /cvs/drupal/drupal/install.php,v
retrieving revision 1.151
diff -u -p -r1.151 install.php
--- install.php	22 Jan 2009 19:31:07 -0000	1.151
+++ install.php	2 Feb 2009 01:16:39 -0000
@@ -48,6 +48,9 @@ function install_main() {
   // Set up $language, so t() caller functions will still work.
   drupal_init_language();
 
+  // Set up cache system.
+  require_once DRUPAL_ROOT . '/includes/cache.inc';
+
   // Load module basics (needed for hook invokes).
   include_once DRUPAL_ROOT . '/includes/module.inc';
   include_once DRUPAL_ROOT . '/includes/session.inc';
@@ -67,9 +70,7 @@ function install_main() {
     // Since we have a database connection, we use the normal cache system.
     // This is important, as the installer calls into the Drupal system for
     // the clean URL checks, so we should maintain the cache properly.
-    require_once DRUPAL_ROOT . '/includes/cache.inc';
-    $conf['cache_inc'] = 'includes/cache.inc';
-
+    $conf['handlers']['defaults']['cache'] = array('class' => 'drupalCache', 'file' => 'includes/cache-default.inc');
     // Initialize the database system. Note that the connection
     // won't be initialized until it is actually requested.
     require_once DRUPAL_ROOT . '/includes/database/database.inc';
@@ -85,8 +86,7 @@ function install_main() {
     // for cached data will fail, we temporarily replace the normal cache
     // system with a stubbed-out version that short-circuits the actual
     // caching process and avoids any errors.
-    require_once DRUPAL_ROOT . '/includes/cache-install.inc';
-    $conf['cache_inc'] = 'includes/cache-install.inc';
+    $conf['handlers']['defaults']['cache'] = array('class' => 'drupalCacheInstall', 'file' => 'includes/cache-install.inc');
 
     $task = NULL;
   }
Index: sites/default/default.settings.php
===================================================================
RCS file: /cvs/drupal/drupal/sites/default/default.settings.php,v
retrieving revision 1.18
diff -u -p -r1.18 default.settings.php
--- sites/default/default.settings.php	23 Dec 2008 19:59:17 -0000	1.18
+++ sites/default/default.settings.php	2 Feb 2009 01:16:39 -0000
@@ -46,7 +46,7 @@
 /**
  * Database settings:
  *
- * The $databases array specifies the database connection or 
+ * The $databases array specifies the database connection or
  * connections that Drupal may use.  Drupal is able to connect
  * to multiple databases, including multiple types of databases,
  * during the same request.
@@ -63,7 +63,7 @@
  *   'port' => 3306,
  * );
  *
- * The "driver" property indicates what Drupal database driver the 
+ * The "driver" property indicates what Drupal database driver the
  * connection should use.  This is usually the same as the name of the
  * database type, such as mysql or sqlite, but not always.  The other
  * properties will vary depending on the driver.  For SQLite, you must
@@ -80,7 +80,7 @@
  * A target database allows Drupal to try to send certain queries to a
  * different database if it can but fall back to the default connection if not.
  * That is useful for master/slave replication, as Drupal may try to connect
- * to a slave server when appropriate and if one is not available will simply 
+ * to a slave server when appropriate and if one is not available will simply
  * fall back to the single master server.
  *
  * The general format for the $databases array is as follows:
@@ -311,3 +311,19 @@ ini_set('session.cookie_lifetime', 20000
 # $conf['blocked_ips'] = array(
 #   'a.b.c.d',
 # );
+
+/**
+ * Handers:
+ *
+ * Then handlers array specifies the handlers configuration.
+ *
+ * @todo add more detailed handler information.
+ */
+$handlers = array(
+  'defaults' => array(
+    'cache' => array(
+      'class' => 'drupalCache',
+      'file' => 'includes/cache-default.inc',
+    ),
+  ),
+);
\ No newline at end of file
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.269
diff -u -p -r1.269 bootstrap.inc
--- includes/bootstrap.inc	31 Jan 2009 16:50:56 -0000	1.269
+++ includes/bootstrap.inc	2 Feb 2009 01:16:40 -0000
@@ -457,7 +457,7 @@ function conf_init() {
   global $base_url, $base_path, $base_root;
 
   // Export the following settings.php variables to the global namespace
-  global $databases, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access;
+  global $databases, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access, $handlers;
   $conf = array();
 
   if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) {
@@ -1130,14 +1130,12 @@ function _drupal_bootstrap($phase) {
       break;
 
     case DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE:
-      // Allow specifying special cache handlers in settings.php, like
-      // using memcached or files for storing cache information.
-      require_once DRUPAL_ROOT . '/' . variable_get('cache_inc', 'includes/cache.inc');
-      // If the page_cache_fastpath is set to TRUE in settings.php and
-      // page_cache_fastpath (implemented in the special implementation of
-      // cache.inc) printed the page and indicated this with a returned TRUE
-      // then we are done.
-      if (variable_get('page_cache_fastpath', FALSE) && page_cache_fastpath()) {
+      require_once DRUPAL_ROOT . '/includes/cache.inc';
+      // If the page_cache_fastpath is set to TRUE in settings.php then page
+      // caching is handled by something other than the default so we can try
+      // retrieving.
+      if (variable_get('page_cache_fastpath', FALSE) && ($cache = page_get_cache(TRUE))) {
+        drupal_page_cache_header($cache);
         exit;
       }
       break;
@@ -1178,6 +1176,7 @@ function _drupal_bootstrap($phase) {
     case DRUPAL_BOOTSTRAP_VARIABLES:
       // Initialize configuration variables, using values from settings.php if available.
       $conf = variable_init(isset($conf) ? $conf : array());
+      handler_init($conf);
       break;
 
     case DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE:
@@ -1615,3 +1614,57 @@ function registry_rebuild() {
 /**
  * @} End of "ingroup registry".
  */
+
+/**
+ * Get the handler configuration for a slot and target.
+ *
+ * @param $slot
+ *   The internal ID of the slot for which we want to set a handler, for
+ *   example cache or session.
+ * @param $target
+ *   The target for which we want set the handler, for cache it can be
+ *   cache_page, cache_filter etc.
+ * @return
+ *   A handler object.
+ */
+function handler($slot, $target = 'default') {
+  static $instances = array();
+  global $handlers;
+  if (!isset($instances['handlers'][$slot][$target])) {
+    if (isset($handlers['handlers'][$slot][$target])) {
+      $instances['handlers'][$slot][$target] = _handler_instantiate($handlers['handlers'][$slot][$target], $target);
+    }
+    else {
+      $instances['handlers'][$slot][$target] = FALSE;
+      if (!isset($instances['defaults'][$slot])) {
+        $instances['defaults'][$slot] = _handler_instantiate($handlers['defaults'][$slot]);
+      }
+    }
+  }
+  return !empty($instances['handlers'][$slot][$target]) ? $instances['handlers'][$slot][$target] : $instances['defaults'][$slot];
+}
+
+/**
+ * Instantiate a handler.
+ *
+ * @param $handler
+ *   An array, containing class and optionally file as described in
+ *   hook_handler.
+ * @param $target
+ *   An optional target, @see handler for more.
+ */
+function _handler_instantiate($handler, $target = NULL) {
+  if (!empty($handler['file'])) {
+    require_once DRUPAL_ROOT . '/' . $handler['file'];
+  }
+  $class = $handler['class'];
+  return isset($target) ? new $class($target) : new $class;
+}
+
+/**
+ * Load handlers from the variables.
+ */
+function handler_init(&$conf) {
+  global $handlers;
+  $handlers = $conf['handlers'];
+}
Index: includes/cache-install.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/cache-install.inc,v
retrieving revision 1.2
diff -u -p -r1.2 cache-install.inc
--- includes/cache-install.inc	7 Aug 2007 08:39:35 -0000	1.2
+++ includes/cache-install.inc	2 Feb 2009 01:16:40 -0000
@@ -11,14 +11,14 @@
  * on performance.
  */
 
-function cache_get($key, $table = 'cache') {
-  return FALSE;
-}
+class drupalCacheInstall {
+  function get($key, $table = 'cache') {
+    return FALSE;
+  }
 
-function cache_set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $headers = NULL) {
-  return;
-}
+  function set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $headers = NULL) {
+  }
 
-function cache_clear_all($cid = NULL, $table = NULL, $wildcard = FALSE) {
-  return;
+  function clear_all($cid = NULL, $table = NULL, $wildcard = FALSE) {
+  }
 }
Index: includes/cache.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/cache.inc,v
retrieving revision 1.27
diff -u -p -r1.27 cache.inc
--- includes/cache.inc	12 Oct 2008 04:30:05 -0000	1.27
+++ includes/cache.inc	2 Feb 2009 01:16:40 -0000
@@ -15,48 +15,7 @@
  * @return The cache or FALSE on failure.
  */
 function cache_get($cid, $table = 'cache') {
-  global $user;
-
-  // Garbage collection necessary when enforcing a minimum cache lifetime
-  $cache_flush = variable_get('cache_flush', 0);
-  if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= REQUEST_TIME)) {
-    // Reset the variable immediately to prevent a meltdown in heavy load situations.
-    variable_set('cache_flush', 0);
-    // Time to flush old cache data
-    db_delete($table)
-      ->condition('expire', CACHE_PERMANENT, '<>')
-      ->condition('expire', $cache_flush, '<=')
-      ->execute();
-  }
-
-  $cache = db_query("SELECT data, created, headers, expire, serialized FROM {" . $table . "} WHERE cid = :cid", array(':cid' => $cid))->fetchObject();
-  if (isset($cache->data)) {
-    // If the data is permanent or we're not enforcing a minimum cache lifetime
-    // always return the cached data.
-    if ($cache->expire == CACHE_PERMANENT || !variable_get('cache_lifetime', 0)) {
-      if ($cache->serialized) {
-        $cache->data = unserialize($cache->data);
-      }
-    }
-    // If enforcing a minimum cache lifetime, validate that the data is
-    // currently valid for this user before we return it by making sure the
-    // cache entry was created before the timestamp in the current session's
-    // cache timer. The cache variable is loaded into the $user object by
-    // _sess_read() in session.inc.
-    else {
-      if ($user->cache > $cache->created) {
-        // This cache data is too old and thus not valid for us, ignore it.
-        return FALSE;
-      }
-      else {
-        if ($cache->serialized) {
-          $cache->data = unserialize($cache->data);
-        }
-      }
-    }
-    return $cache;
-  }
-  return FALSE;
+  return handler('cache', $table)->get($cid, $table);
 }
 
 /**
@@ -105,25 +64,7 @@ function cache_get($cid, $table = 'cache
  *   A string containing HTTP header information for cached pages.
  */
 function cache_set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $headers = NULL) {
-  $fields = array(
-    'serialized' => 0,
-    'created' => REQUEST_TIME,
-    'expire' => $expire,
-    'headers' => $headers,
-  );
-  if (!is_string($data)) {
-    $fields['data'] = serialize($data);
-    $fields['serialized'] = 1;
-  }
-  else {
-    $fields['data'] = $data;
-    $fields['serialized'] = 0;
-  }
-
-  db_merge($table)
-    ->key(array('cid' => $cid))
-    ->fields($fields)
-    ->execute();
+  return handler('cache', $table)->set($cid, $data, $table, $expire, $headers);
 }
 
 /**
@@ -145,62 +86,89 @@ function cache_set($cid, $data, $table =
  *   match. If '*' is given as $cid, the table $table will be emptied.
  */
 function cache_clear_all($cid = NULL, $table = NULL, $wildcard = FALSE) {
-  global $user;
-
-  if (!isset($cid) && !isset($table)) {
-    // Clear the block cache first, so stale data will
-    // not end up in the page cache.
-    cache_clear_all(NULL, 'cache_block');
-    cache_clear_all(NULL, 'cache_page');
-    return;
-  }
-
-  if (empty($cid)) {
-    if (variable_get('cache_lifetime', 0)) {
-      // We store the time in the current user's $user->cache variable which
-      // will be saved into the sessions table by _sess_write(). We then
-      // simulate that the cache was flushed for this user by not returning
-      // cached data that was cached before the timestamp.
-      $user->cache = REQUEST_TIME;
+  return handler('cache', $table)->clear_all($cid, $table, $wildcard);
+}
 
-      $cache_flush = variable_get('cache_flush', 0);
-      if ($cache_flush == 0) {
-        // This is the first request to clear the cache, start a timer.
-        variable_set('cache_flush', REQUEST_TIME);
-      }
-      elseif (REQUEST_TIME > ($cache_flush + variable_get('cache_lifetime', 0))) {
-        // Clear the cache for everyone, cache_flush_delay seconds have
-        // passed since the first request to clear the cache.
-        db_delete($table)
-          ->condition('expire', CACHE_PERMANENT, '<>')
-          ->condition('expire', REQUEST_TIME, '<')
-          ->execute();
-        variable_set('cache_flush', 0);
-      }
-    }
-    else {
-      // No minimum cache lifetime, flush all temporary cache entries now.
-      db_delete($table)
-        ->condition('expire', CACHE_PERMANENT, '<>')
-        ->condition('expire', REQUEST_TIME, '<')
-        ->execute();
-    }
-  }
-  else {
-    if ($wildcard) {
-      if ($cid == '*') {
-        db_delete($table)->execute();
-      }
-      else {
-        db_delete($table)
-          ->condition('cid', $cid . '%', 'LIKE')
-          ->execute();
-      }
-    }
-    else {
-      db_delete($table)
-        ->condition('cid', $cid)
-        ->execute();
-    }
-  }
+interface drupalCacheInterface {
+  /**
+   * Return data from the persistent cache. Data may be stored as either plain
+   * text or as serialized data, but get will automatically return unserialized
+   * objects and arrays.
+   *
+   * @param $cid
+   *   The cache ID of the data to retrieve.
+   * @param $table
+   *   The table $table to store the data in. Valid core values are
+   *   'cache_filter', 'cache_menu', 'cache_page', or 'cache' for
+   *   the default cache.
+   * @return The cache or FALSE on failure.
+   */
+  function get($cid, $table = 'cache');
+
+  /**
+   * Store data in the persistent cache.
+   *
+   * The persistent cache is split up into four database
+   * tables. Contributed modules can add additional tables.
+   *
+   * 'cache_page': This table stores generated pages for anonymous
+   * users. This is the only table affected by the page cache setting on
+   * the administrator panel.
+   *
+   * 'cache_menu': Stores the cachable part of the users' menus.
+   *
+   * 'cache_filter': Stores filtered pieces of content. This table is
+   * periodically cleared of stale entries by cron.
+   *
+   * 'cache': Generic cache storage table.
+   *
+   * The reasons for having several tables are as follows:
+   *
+   * - smaller tables allow for faster selects and inserts
+   * - we try to put fast changing cache items and rather static
+   *   ones into different tables. The effect is that only the fast
+   *   changing tables will need a lot of writes to disk. The more
+   *   static tables will also be better cachable with MySQL's query cache
+   *
+   * @param $cid
+   *   The cache ID of the data to store.
+   * @param $data
+   *   The data to store in the cache. Complex data types will be automatically
+   *   serialized before insertion.
+   *   Strings will be stored as plain text and not serialized.
+   * @param $table
+   *   The table $table to store the data in. Valid core values are
+   *   'cache_filter', 'cache_menu', 'cache_page', or 'cache'.
+   * @param $expire
+   *   One of the following values:
+   *   - CACHE_PERMANENT: Indicates that the item should never be removed unless
+   *     explicitly told to using clear_all() with a cache ID.
+   *   - CACHE_TEMPORARY: Indicates that the item should be removed at the next
+   *     general cache wipe.
+   *   - A Unix timestamp: Indicates that the item should be kept at least until
+   *     the given time, after which it behaves like CACHE_TEMPORARY.
+   * @param $headers
+   *   A string containing HTTP header information for cached pages.
+   */
+  function set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $headers = NULL);
+
+  /**
+   *
+   * Expire data from the cache. If called without arguments, expirable
+   * entries will be cleared from the cache_page and cache_block tables.
+   *
+   * @param $cid
+   *   If set, the cache ID to delete. Otherwise, all cache entries that can
+   *   expire are deleted.
+   *
+   * @param $table
+   *   If set, the table $table to delete from. Mandatory
+   *   argument if $cid is set.
+   *
+   * @param $wildcard
+   *   If set to TRUE, the $cid is treated as a substring
+   *   to match rather than a complete ID. The match is a right hand
+   *   match. If '*' is given as $cid, the table $table will be emptied.
+   */
+  function clear_all($cid = NULL, $table = NULL, $wildcard = FALSE);
 }
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.860
diff -u -p -r1.860 common.inc
--- includes/common.inc	31 Jan 2009 19:07:45 -0000	1.860
+++ includes/common.inc	2 Feb 2009 01:16:42 -0000
@@ -4142,3 +4142,101 @@ function _drupal_flush_css_js() {
   }
   variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19));
 }
+
+/**
+ * Rebuild the handler information.
+ *
+ * @param $revert_to_defaults
+ *   Delete all the specific classes previously set by handler_set. Use with
+ *   extreme caution. Defaults to FALSE.
+ */
+function handler_rebuild($revert_to_defaults = FALSE) {
+  global $handlers;
+  if ($revert_to_defaults) {
+    unset($handlers['handlers']);
+  }
+  // We only keep the default handler information, the rest is documentation.
+  foreach (module_invoke_all('handler') as $slot => $definitions) {
+    $handlers['defaults'][$slot] = array('class' => $definitions['default']['class']);
+    if (isset($definitions['default']['file'])) {
+      $handlers['defaults'][$slot]['file'] = $definitions['default']['file'];
+    }
+  }
+  variable_set('handlers', $handlers);
+}
+
+/**
+ * Gets the information for a given handler.
+ * @param $slot
+ *   The internal ID of the slot for which we want to set a handler, for
+ *   example cache or session.
+ * @param $target
+ *   The target for which we want set the handler, for cache it can be
+ *   cache_page, cache_filter etc.
+ * @param $return
+ *   Returns an associative array. 'definition' is an array containing
+ *   description, class and optionally file as described in hook_handler.
+ *   'handler' is the name of the handler and 'target' is either the target
+ *   'passed in or NULL if there is no specific handler set for this target.
+ *   This is the same $target as handler_set expects it.
+ */
+function handler_get($slot, $target) {
+  global $handlers;
+  if (isset($handlers['handlers'][$slot][$target])) {
+    $class = $handlers['handlers'][$slot][$target]['class'];
+  }
+  else {
+   $class = $handlers['defaults'][$slot]['class'];
+   $target = NULL;
+  }
+  foreach (module_invoke_all('handler') as $slot => $definitons) {
+    foreach ($definitons as $handler => $definition) {
+      if ($definition['class'] == $class) {
+        return array('target' => $target, 'handler' => $handler, 'definition' => $definition);
+      }
+    }
+  }
+}
+
+/**
+ * Set a specific handler for a slot and optionally a target.
+ *
+ * @param $slot
+ *   The internal ID of the slot for which we want to set a handler, for
+ *   example cache or session.
+ * @param $handler
+ *   The identifier of this handler, for example apc as defined in
+ *   system.api.php
+ * @param $target
+ *   The target for which we want set the handler, for cache it can be
+ *   cache_page, cache_filter etc. Defaults to NULL, setting a default handler.
+ */
+function handler_set($slot, $handler, $target = NULL) {
+  global $handlers;
+  $handler_info = module_invoke_all('handler');;
+  $definition = array('class' => $handler_info[$slot][$handler]['class']);
+  // We only keep the class, the rest is documentation.
+  if (isset($target)) {
+    $handlers['handlers'][$slot][$target] = $definition;
+  }
+  else {
+    $handlers['defaults'][$slot] = $definition;
+  }
+  variable_set('handlers', $handlers);
+}
+
+/**
+ * Delete a specific handler for a target.
+ *
+ * @param $slot
+ *   The internal ID of the slot for which we want to set a handler, for
+ *   example cache or session.
+ * @param $target
+ *   The target to be deleted, for cache it can be cache_page,
+ *   cache_filter etc.
+ */
+function handler_del($slot, $target) {
+  global $handlers;
+  unset($handlers['handlers'][$slot][$target]);
+  variable_set('handlers', $handlers);
+}
Index: modules/simpletest/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v
retrieving revision 1.25
diff -u -p -r1.25 common.test
--- modules/simpletest/tests/common.test	31 Jan 2009 19:07:45 -0000	1.25
+++ modules/simpletest/tests/common.test	2 Feb 2009 01:16:43 -0000
@@ -832,3 +832,109 @@ class DrupalErrorCollectionUnitTest exte
     }
   }
 }
+
+/**
+ * Test class for the handler system.
+ */
+class HandlerTestCase extends DrupalWebTestCase {
+  function getInfo() {
+    return array(
+      'name' => t('Handlers'),
+      'description' => t('Performs tests on the handler system.'),
+      'group' => t('System'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('handlers_test');
+  }
+
+  function tearDown() {
+    handler_rebuild(TRUE);
+    parent::tearDown();
+  }
+
+  /**
+   * Test getting the default handler configuration for a slot.
+   */
+  function testGetDefaultHandler() {
+    $handler_info = handler_get('handlers_test', 'default');
+    $handler_definition = handlers_test_handler();
+    $handler_expected = array(
+      'target' => NULL,
+      'handler' => 'default',
+      'definition' => $handler_definition['handlers_test']['default'],
+    );
+    $this->assertEqual($handler_info, $handler_expected, t("Retrieved default handler configuration."));
+  }
+
+  /**
+   * Test setting a handler configuration for a slot.
+   */
+  function testSetHandler() {
+    handler_set('handlers_test', 'test', 'default');
+    global $handlers;
+    $this->assertEqual($handlers['handlers']['handlers_test']['default']['class'], 'handlersTestOther', t("Set handler configuration."));
+  }
+
+  /**
+   * Test setting multiple targets on a handler.
+   */
+  function testSetHandlerMultipleTargets() {
+    handler_set('handlers_test', 'test', 'test_target');
+    handler_set('handlers_test', 'test2', 'test_target2');
+    global $handlers;
+    $this->assertEqual($handlers['handlers']['handlers_test']['test_target']['class'], 'handlersTestOther', t("Set handler to test target."));
+    $this->assertEqual($handlers['handlers']['handlers_test']['test_target2']['class'], 'handlersTestOther2', t("Set handler to second test target."));
+  }
+
+  /**
+   * Test getting a set handler configuration for a slot.
+   */
+  function testGetHanlder() {
+    handler_set('handlers_test', 'test', 'test_target');
+    $handler_info = handler_get('handlers_test', 'test_target');
+    $definition = handlers_test_handler();
+    $handler_expected = array(
+      'target' => 'test_target',
+      'handler' => 'test',
+      'definition' => $definition['handlers_test']['test'],
+    );
+    $this->assertEqual($handler_info, $handler_expected, t("Retrieve configured handler configuration."));
+  }
+
+  /**
+   * Test deleting a set handler configuration for a slot.
+   */
+  function testDelHandler() {
+    handler_set('handlers_test', 'test', 'test_target');
+    handler_del('handlers_test', 'test_target');
+    global $handlers;
+    $this->assertFalse(isset($handlers['handlers']['handlers_test']['test_target']), t("Delete a handlers configuration."));
+  }
+
+  /**
+   * Test getting a default handler.
+   */
+  function testDefaultHanlder() {
+    $this->assertEqual(handler('handlers_test')->getClass(), 'handlersTestDefault', t("Retrieve default handler."));
+    $this->assertEqual(handler('handlers_test', 'test_target')->getClass(), 'handlersTestDefault', t("Retrieve default handler for target other than default."));
+  }
+
+  /**
+   * Test getting a handler.
+   */
+  function testHandler() {
+    handler_set('handlers_test', 'test', 'target');
+    $this->assertEqual(handler('handlers_test', 'target')->getClass(), 'handlersTestOther', t("Retrieve handler."));
+  }
+
+  /**
+   * Test getting handlers for multiple targets.
+   */
+  function testMultipleTargets() {
+    handler_set('handlers_test', 'test', 'test_target');
+    handler_set('handlers_test', 'test2', 'test_target2');
+    $this->assertNotEqual(handler('handlers_test', 'test_target')->getClass(), handler('handlers_test', 'test_target2')->getClass(), t("Retrieve multiple handlers."));
+  }
+}
\ No newline at end of file
Index: modules/simpletest/tests/handlers_test.info
===================================================================
RCS file: modules/simpletest/tests/handlers_test.info
diff -N modules/simpletest/tests/handlers_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/handlers_test.info	2 Feb 2009 01:16:43 -0000
@@ -0,0 +1,8 @@
+; $Id$
+name = "Handlers test"
+description = "Support module for handler testing."
+package = Testing
+version = VERSION
+core = 7.x
+files[] = handlers_test.module
+hidden = TRUE
\ No newline at end of file
Index: modules/simpletest/tests/handlers_test.module
===================================================================
RCS file: modules/simpletest/tests/handlers_test.module
diff -N modules/simpletest/tests/handlers_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/handlers_test.module	2 Feb 2009 01:16:43 -0000
@@ -0,0 +1,56 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_slot_info.
+ */
+function handlers_test_slot_info() {
+  return array(
+    'handlers_test' => array(
+      'description' => t('Handlers test slot.'),
+      'interface' => 'drupalHandlersTestInterface',
+    ),
+  );
+}
+
+function handlers_test_handler() {
+  return array(
+    'handlers_test' => array(
+      'default' => array(
+        'description' => t('Default handler for the test case.'),
+        'class' => 'handlersTestDefault',
+      ),
+      'test' => array(
+        'description' => t('Test class for handlers test.'),
+        'class' => 'handlersTestOther',
+      ),
+      'test2' => array(
+        'description' => t('Second test class for handlers test.'),
+        'class' => 'handlersTestOther2',
+      ),
+    ),
+  );
+}
+
+/**
+ * Handler test class
+ */
+class handlersTestDefault {
+  protected $class = NULL;
+
+  function __construct($class = NULL) {
+    $this->class = $class;
+  }
+
+  function getClass() {
+    return get_class($this);
+  }
+}
+
+class handlersTestOther extends handlersTestDefault {
+
+}
+
+class handlersTestOther2 extends handlersTestDefault {
+
+}
\ No newline at end of file
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.123
diff -u -p -r1.123 system.admin.inc
--- modules/system/system.admin.inc	27 Jan 2009 00:22:26 -0000	1.123
+++ modules/system/system.admin.inc	2 Feb 2009 01:16:45 -0000
@@ -575,6 +575,7 @@ function system_modules($form_state = ar
   drupal_theme_rebuild();
   node_types_rebuild();
   cache_clear_all('schema', 'cache');
+  handler_rebuild();
   // Get current list of modules.
   $files = module_rebuild_cache();
 
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.17
diff -u -p -r1.17 system.api.php
--- modules/system/system.api.php	31 Jan 2009 16:50:57 -0000	1.17
+++ modules/system/system.api.php	2 Feb 2009 01:16:46 -0000
@@ -212,7 +212,7 @@ function hook_js_alter(&$javascript) {
  *   $page['content']['nodes'][$nid]['#node']
  *   // The results pager.
  *   $page['content']['pager']
- * @code 
+ * @code
  *
  * Blocks may be referenced by their module/delta pair within a region:
  * @code
@@ -1636,5 +1636,66 @@ function hook_disable() {
 }
 
 /**
+ * Define one or more slots for the handlers system.
+ *
+ * This hook should return a nested array of slot definitions.  Each key
+ * in the array is the machine-readable slot name, and its value is an array
+ * of values that define the slot:
+ *
+ * description
+ *   A human-readable description of what the slot is for.
+ * interface
+ *   The PHP interface that defines this slot.
+ * @return
+ *   An array of slot definition arrays.
+ */
+function hook_slot_info() {
+  return array(
+    'cache' => array(
+      'description' => 'Caching system',
+      'interface' => 'drupalCacheInterface',
+    ),
+  );
+}
+
+/**
+ * Define one or more handlers in the system.
+ *
+ * This hook should return a three dimension array of handler definitions.  The
+ * first key is an existing slot, the second key is the name of the handler, the
+ * rest is a handler definition.
+ *
+ * Each handler definition is an associative array of values that define the
+ * handler with the following possible keys:
+ *
+ * description
+ *   A human readable description of this handler.
+ * class
+ *   The PHP class that defines this handler.  The class must implement the
+ *   interface defined for this slot, but otherwise may be defined in any way
+ *   desired, including implementing the interface directly or indirectly by
+ *   subclassing another class that implements that interface. It may also be
+ *   placed in any file, depending on what would provide the most performance
+ *   for autoloading.
+ * file (optional)
+ *   A file to load which includes the class being loaded. Note that this should
+ *   only be used in cases where the registry is not available to automatincally load
+ *   the class.
+ *
+ * @return
+ *   An array of handler definition arrays.
+ */
+function hook_handler() {
+  return array(
+    'cache' => array(
+      'apc' => array(
+        'description' => t('Stores the filter cache in APC'),
+        'class' => 'apcCache',
+      ),
+    ),
+  );
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.664
diff -u -p -r1.664 system.module
--- modules/system/system.module	1 Feb 2009 06:48:15 -0000	1.664
+++ modules/system/system.module	2 Feb 2009 01:16:47 -0000
@@ -399,6 +399,33 @@ function system_elements() {
 }
 
 /**
+ * Implementation of hook_slot_info.
+ */
+function system_slot_info() {
+  return array(
+    'cache' => array(
+      'description' => t('Caching system'),
+      'interface' => 'drupalCacheInterface',
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_handler().
+ */
+function system_handler() {
+  return array(
+    'cache' => array(
+      'default' => array(
+        'description' => t('Caching in the default database'),
+        'class' => 'drupalCache',
+        'file' => 'includes/cache-default.inc',
+      ),
+    ),
+  );
+}
+
+/**
  * Implementation of hook_menu().
  */
 function system_menu() {
