diff --git a/api/src/Cache.php b/api/src/Cache.php new file mode 100644 index 0000000000..e0215da9ad --- /dev/null +++ b/api/src/Cache.php @@ -0,0 +1,744 @@ + + * @copyright (c) 2009-16 by Ralf Becker + * @version $Id$ + */ + +namespace EGroupware\Api; + +use egw_exception_db; +use egw_exception_wrong_parameter; +use egw_session; + +/** + * Class to manage caching in eGroupware. + * + * It allows to cache on 4 levels: + * a) tree: for all instances/domains runining on a certain source path + * b) instance: for all sessions on a given instance + * c) session: for all requests of a session, same as egw_session::appsession() + * d) request: just for this request (same as using a static variable) + * + * There's a get, a set and a unset method for each level: eg. getTree() or setInstance(), + * as well as a variant allowing to specify the level as first parameter: eg. unsetCache() + * + * getXXX($app,$location,$callback=null,array $callback_params,$expiration=0) + * has three optional parameters allowing to specify: + * 3. a callback if requested data is not yes stored. In that case the callback is called + * and it's value is stored in the cache AND retured + * 4. parameters to pass to the callback as array, see call_user_func_array + * 5. an expiration time in seconds to specify how long data should be cached, + * default 0 means infinit (this time is not garantied and not supported for all levels!) + * + * Data is stored under an application name and a location, like egw_session::appsession(). + * In fact data stored at cache level Api\Cache::SESSION, is stored in the same way as + * egw_session::appsession() so both methods can be used with each other. + * + * The $app parameter should be either the app or the class name, which both are unique. + * + * The tree and instance wide cache uses a certain provider class, to store the data + * eg. in memcached or if there's nothing else configured in the filesystem (eGW's temp_dir). + * + * "Admin >> clear cache and register hooks" allways only clears instance level cache of + * calling instance. It never clears tree level cache, which makes it important to set + * resonable expiry times or think about an other means of clearing that particular item. + * (Not clearing of tree-level cache is important, as regenerating it is an expensive + * operation for a huge scale EGroupware hosting operation.) + * + * Apps needing to talk to multiple EGroupware instances (eg. Stylite Managementserver) + * can use install_id of instance as $level parameter to (set|get|unset)Cache method. + */ +class Cache +{ + /** + * tree-wide storage + */ + const TREE = 'Tree'; + /** + * instance-wide storage + */ + const INSTANCE = 'Instance'; + /** + * session-wide storage + */ + const SESSION = 'Session'; + /** + * request-wide storage + */ + const REQUEST = 'Request'; + + /** + * Default provider for tree and instance data + * + * Can be specified eg. in the header.inc.php by setting: + * $GLOBALS['egw_info']['server']['cache_provider_instance'] and optional + * $GLOBALS['egw_info']['server']['cache_provider_tree'] (defaults to instance) + * + * Default is set (if not set here) after class definition to Cache\Apc or Cache\Files, + * depending on function 'apc_fetch' exists or not + * + * @var array + */ + static $default_provider; // = array('EGroupware\Api\Cache\Files');// array('EGroupware\Api\Cache\Memcache','localhost'); + + /** + * Maximum expiration time, if set unlimited expiration (=0) or bigger expiration times are replaced with that time + * + * @var int + */ + static $max_expiration; + + /** + * Used to determine keys for tree- and instance-level caches + * + * @var string + */ + static $egw_server_root = EGW_SERVER_ROOT; + + /** + * Add some data in the cache, only if the key does not yet exist + * + * @param string $level use Api\Cache::(TREE|INSTANCE) + * @param string $app application storing data + * @param string $location location name for data + * @param mixed $data + * @param int $expiration =0 expiration time in seconds, default 0 = never + * @return boolean true if data could be stored, false otherwise incl. key already existed + */ + static public function addCache($level,$app,$location,$data,$expiration=0) + { + //error_log(__METHOD__."('$level','$app','$location',".array2string($data).",$expiration)"); + switch($level) + { + case self::SESSION: + case self::REQUEST: + throw new egw_exception_wrong_parameter(__METHOD__."('$level', ...) unsupported level parameter!"); + + case self::INSTANCE: + case self::TREE: + default: + if (!($provider = self::get_provider($level))) + { + return false; + } + // limit expiration to configured maximum time + if (isset(self::$max_expiration) && (!$expiration || $expiration > self::$max_expiration)) + { + $expiration = self::$max_expiration; + } + return $provider->add(self::keys($level,$app,$location),$data,$expiration); + } + throw new egw_exception_wrong_parameter(__METHOD__."() unknown level '$level'!"); + } + + /** + * Set some data in the cache + * + * @param string $level use Api\Cache::(TREE|INSTANCE|SESSION|REQUEST) + * @param string $app application storing data + * @param string $location location name for data + * @param mixed $data + * @param int $expiration =0 expiration time in seconds, default 0 = never + * @return boolean true if data could be stored, false otherwise + */ + static public function setCache($level,$app,$location,$data,$expiration=0) + { + //error_log(__METHOD__."('$level','$app','$location',".array2string($data).",$expiration)"); + switch($level) + { + case self::SESSION: + case self::REQUEST: + return call_user_func(array(__CLASS__,'set'.$level),$app,$location,$data,$expiration); + + case self::INSTANCE: + case self::TREE: + default: + if (!($provider = self::get_provider($level))) + { + return false; + } + // limit expiration to configured maximum time + if (isset(self::$max_expiration) && (!$expiration || $expiration > self::$max_expiration)) + { + $expiration = self::$max_expiration; + } + return $provider->set(self::keys($level,$app,$location),$data,$expiration); + } + throw new egw_exception_wrong_parameter(__METHOD__."() unknown level '$level'!"); + } + + /** + * Get some data from the cache + * + * @param string $level use Api\Cache::(TREE|INSTANCE|SESSION|REQUEST) + * @param string $app application storing data + * @param string|array $location location(s) name for data + * @param callback $callback =null callback to get/create the value, if it's not cache + * @param array $callback_params =array() array with parameters for the callback + * @param int $expiration =0 expiration time in seconds, default 0 = never + * @return mixed NULL if data not found in cache (and no callback specified) or + * if $location is an array: location => data pairs for existing location-data, non-existing is not returned + */ + static public function getCache($level,$app,$location,$callback=null,array $callback_params=array(),$expiration=0) + { + switch($level) + { + case self::SESSION: + case self::REQUEST: + foreach((array)$location as $l) + { + $data[$l] = call_user_func(array(__CLASS__,'get'.$level),$app,$l,$callback,$callback_params,$expiration); + } + return is_array($location) ? $data : $data[$l]; + + case self::INSTANCE: + case self::TREE: + default: + if (!($provider = self::get_provider($level))) + { + return null; + } + try { + if (is_array($location)) + { + if (!is_null($callback)) + { + throw new egw_exception_wrong_parameter(__METHOD__."() you can NOT use multiple locations (\$location parameter is an array) together with a callback!"); + } + if (is_a($provider, 'EGroupware\Api\Cache\ProviderMultiple')) + { + $data = $provider->mget($keys=self::keys($level,$app,$location)); + } + else // default implementation calls get multiple times + { + $data = array(); + foreach($location as $l) + { + $data[$l] = $provider->get($keys=self::keys($level,$app,$l)); + if (!isset($data[$l])) unset($data[$l]); + } + } + } + else + { + $data = $provider->get($keys=self::keys($level,$app,$location)); + if (is_null($data) && !is_null($callback)) + { + $data = call_user_func_array($callback,$callback_params); + // limit expiration to configured maximum time + if (isset(self::$max_expiration) && (!$expiration || $expiration > self::$max_expiration)) + { + $expiration = self::$max_expiration; + } + $provider->set($keys,$data,$expiration); + } + } + } + catch(Exception $e) { + unset($e); + $data = null; + } + return $data; + } + throw new egw_exception_wrong_parameter(__METHOD__."() unknown level '$level'!"); + } + + /** + * Unset some data in the cache + * + * @param string $level use Api\Cache::(TREE|INSTANCE|SESSION|REQUEST) + * @param string $app application storing data + * @param string $location location name for data + * @return boolean true if data was set, false if not (like isset()) + */ + static public function unsetCache($level,$app,$location) + { + switch($level) + { + case self::SESSION: + case self::REQUEST: + return call_user_func(array(__CLASS__,'unset'.$level),$app,$location); + + case self::INSTANCE: + case self::TREE: + default: + if (!($provider = self::get_provider($level, false))) + { + return false; + } + return $provider->delete(self::keys($level,$app,$location)); + } + throw new egw_exception_wrong_parameter(__METHOD__."() unknown level '$level'!"); + } + + /** + * Set some data in the cache for the whole source tree (all instances) + * + * @param string $app application storing data + * @param string $location location name for data + * @param mixed $data + * @param int $expiration =0 expiration time in seconds, default 0 = never + * @return boolean true if data could be stored, false otherwise + */ + static public function setTree($app,$location,$data,$expiration=0) + { + //error_log(__METHOD__."('$app','$location',".array2string($data).",$expiration)"); + return self::setCache(self::TREE,$app,$location,$data,$expiration); + } + + /** + * Get some data from the cache for the whole source tree (all instances) + * + * @param string $app application storing data + * @param string $location location name for data + * @param callback $callback =null callback to get/create the value, if it's not cache + * @param array $callback_params =array() array with parameters for the callback + * @param int $expiration =0 expiration time in seconds, default 0 = never + * @return mixed NULL if data not found in cache (and no callback specified) + */ + static public function getTree($app,$location,$callback=null,array $callback_params=array(),$expiration=0) + { + return self::getCache(self::TREE,$app,$location,$callback,$callback_params,$expiration); + } + + /** + * Unset some data in the cache for the whole source tree (all instances) + * + * @param string $app application storing data + * @param string $location location name for data + * @return boolean true if data was set, false if not (like isset()) + */ + static public function unsetTree($app,$location) + { + return self::unsetCache(self::TREE,$app,$location); + } + + /** + * Set some data in the cache for the whole source tree (all instances) + * + * @param string $app application storing data + * @param string $location location name for data + * @param mixed $data + * @param int $expiration =0 expiration time in seconds, default 0 = never + * @return boolean true if data could be stored, false otherwise + */ + static public function setInstance($app,$location,$data,$expiration=0) + { + return self::setCache(self::INSTANCE,$app,$location,$data,$expiration); + } + + /** + * Get some data from the cache for the whole source tree (all instances) + * + * @param string $app application storing data + * @param string $location location name for data + * @param callback $callback =null callback to get/create the value, if it's not cache + * @param array $callback_params =array() array with parameters for the callback + * @param int $expiration =0 expiration time in seconds, default 0 = never + * @return mixed NULL if data not found in cache (and no callback specified) + */ + static public function getInstance($app,$location,$callback=null,array $callback_params=array(),$expiration=0) + { + return self::getCache(self::INSTANCE,$app,$location,$callback,$callback_params,$expiration); + } + + /** + * Unset some data in the cache for the whole source tree (all instances) + * + * @param string $app application storing data + * @param string $location location name for data + * @return boolean true if data was set, false if not (like isset()) + */ + static public function unsetInstance($app,$location) + { + return self::unsetCache(self::INSTANCE,$app,$location); + } + + /** + * Set some data in the cache for the whole source tree (all instances) + * + * @param string $app application storing data + * @param string $location location name for data + * @param mixed $data + * @param int $expiration =0 expiration time in seconds, default 0 = never + * @return boolean true if data could be stored, false otherwise + */ + static public function setSession($app,$location,$data,$expiration=0) + { + unset($expiration); // not used, but required by function signature + if (isset($_SESSION[egw_session::EGW_SESSION_ENCRYPTED])) + { + if (egw_session::ERROR_LOG_DEBUG) error_log(__METHOD__.' called after session was encrypted --> ignored!'); + return false; // can no longer store something in the session, eg. because commit_session() was called + } + $_SESSION[egw_session::EGW_APPSESSION_VAR][$app][$location] = $data; + + return true; + } + + /** + * Get some data from the cache for the whole source tree (all instances) + * + * Returns a reference to the var in the session! + * + * @param string $app application storing data + * @param string $location location name for data + * @param callback $callback =null callback to get/create the value, if it's not cache + * @param array $callback_params =array() array with parameters for the callback + * @param int $expiration =0 expiration time in seconds, default 0 = never + * @return mixed NULL if data not found in cache (and no callback specified) + */ + static public function &getSession($app,$location,$callback=null,array $callback_params=array(),$expiration=0) + { + unset($expiration); // not used, but required by function signature + if (isset($_SESSION[egw_session::EGW_SESSION_ENCRYPTED])) + { + if (egw_session::ERROR_LOG_DEBUG) error_log(__METHOD__.' called after session was encrypted --> ignored!'); + return null; // can no longer store something in the session, eg. because commit_session() was called + } + if (!isset($_SESSION[egw_session::EGW_APPSESSION_VAR][$app][$location]) && !is_null($callback)) + { + $_SESSION[egw_session::EGW_APPSESSION_VAR][$app][$location] = call_user_func_array($callback,$callback_params); + } + return $_SESSION[egw_session::EGW_APPSESSION_VAR][$app][$location]; + } + + /** + * Unset some data in the cache for the whole source tree (all instances) + * + * @param string $app application storing data + * @param string $location location name for data + * @return boolean true if data was set, false if not (like isset()) + */ + static public function unsetSession($app,$location) + { + if (isset($_SESSION[egw_session::EGW_SESSION_ENCRYPTED])) + { + if (egw_session::ERROR_LOG_DEBUG) error_log(__METHOD__.' called after session was encrypted --> ignored!'); + return false; // can no longer store something in the session, eg. because commit_session() was called + } + if (!isset($_SESSION[egw_session::EGW_APPSESSION_VAR][$app][$location])) + { + return false; + } + unset($_SESSION[egw_session::EGW_APPSESSION_VAR][$app][$location]); + + return true; + } + + /** + * Static varible to cache request wide + * + * @var array + */ + private static $request_cache = array(); + + /** + * Set some data in the cache for the whole source tree (all instances) + * + * @param string $app application storing data + * @param string $location location name for data + * @param mixed $data + * @param int $expiration =0 expiration time is NOT used for REQUEST! + * @return boolean true if data could be stored, false otherwise + */ + static public function setRequest($app,$location,$data,$expiration=0) + { + unset($expiration); // not used, but required by function signature + self::$request_cache[$app][$location] = $data; + + return true; + } + + /** + * Get some data from the cache for the whole source tree (all instances) + * + * @param string $app application storing data + * @param string $location location name for data + * @param callback $callback =null callback to get/create the value, if it's not cache + * @param array $callback_params =array() array with parameters for the callback + * @param int $expiration =0 expiration time is NOT used for REQUEST! + * @return mixed NULL if data not found in cache (and no callback specified) + */ + static public function getRequest($app,$location,$callback=null,array $callback_params=array(),$expiration=0) + { + unset($expiration); // not used, but required by function signature + if (!isset(self::$request_cache[$app][$location]) && !is_null($callback)) + { + self::$request_cache[$app][$location] = call_user_func_array($callback,$callback_params); + } + return self::$request_cache[$app][$location]; + } + + /** + * Unset some data in the cache for the whole source tree (all instances) + * + * @param string $app application storing data + * @param string $location location name for data + * @return boolean true if data was set, false if not (like isset()) + */ + static public function unsetRequest($app,$location) + { + if (!isset(self::$request_cache[$app][$location])) + { + return false; + } + unset(self::$request_cache[$app][$location]); + + return true; + } + + /** + * Get a caching provider for tree or instance level + * + * The returned provider already has an opened connection + * + * @param string $level Api\Cache::(TREE|INSTANCE) or install_id + * @param boolean $log_not_found =true false do not log if no provider found, used eg. to supress error via unsetCache during installation + * @return Api\Cache\Provider + */ + static protected function get_provider($level, $log_not_found=true) + { + static $providers = array(); + + if ($level != self::TREE) $level = self::INSTANCE; + + if (!isset($providers[$level])) + { + $params = $GLOBALS['egw_info']['server']['cache_provider_'.strtolower($level)]; + if (!isset($params) && $level == self::INSTANCE && isset(self::$default_provider)) + { + $params = self::$default_provider; + } + if (!isset($params)) + { + if ($level == self::TREE) // if no tree level provider use the instance level one + { + $providers[$level] = self::get_provider(self::INSTANCE); + } + else + { + $providers[$level] = false; // no provider specified + $reason = 'no provider specified'; + } + } + elseif (!$params) + { + $providers[$level] = false; // cache for $level disabled + $reason = "cache for $level disabled"; + } + else + { + if (!is_array($params)) $params = (array)$params; + + $class = array_shift($params); + if (!class_exists($class)) + { + $providers[$level] = false; // provider class not found + $reason = "provider $class not found"; + } + else + { + try + { + $providers[$level] = new $class($params); + } + catch(Exception $e) + { + $providers[$level] = false; // eg. could not open connection to backend + $reason = "error instanciating provider $class: ".$e->getMessage(); + } + } + } + if (!$providers[$level] && $log_not_found) error_log(__METHOD__."($level) no provider found ($reason)!".function_backtrace()); + } + //error_log(__METHOD__."($level) = ".array2string($providers[$level]).', cache_provider='.array2string($GLOBALS['egw_info']['server']['cache_provider_'.strtolower($level)])); + return $providers[$level]; + } + + /** + * Get a system configuration, even if in setup and it's not read + * + * @param string $name + * @param boolean $throw =true throw an exception, if we can't retriev the value + * @return string|boolean string with config or false if not found and !$throw + */ + static public function get_system_config($name,$throw=true) + { + if(!isset($GLOBALS['egw_info']['server'][$name])) + { + if (isset($GLOBALS['egw_setup']) && isset($GLOBALS['egw_setup']->db) || $GLOBALS['egw']->db) + { + $db = $GLOBALS['egw']->db ? $GLOBALS['egw']->db : $GLOBALS['egw_setup']->db; + + try { + if (($rs = $db->select(config::TABLE,'config_value',array( + 'config_app' => 'phpgwapi', + 'config_name' => $name, + ),__LINE__,__FILE__))) + { + $GLOBALS['egw_info']['server'][$name] = $rs->fetchColumn(); + } + else + { + error_log(__METHOD__."('$name', $throw) config value NOT found!");//.function_backtrace()); + } + } + catch(egw_exception_db $e) + { + if ($throw) error_log(__METHOD__."('$name', $throw) cound NOT query value: ".$e->getMessage());//.function_backtrace()); + } + } + if (!$GLOBALS['egw_info']['server'][$name] && $throw) + { + throw new Exception (__METHOD__."($name) \$GLOBALS['egw_info']['server']['$name'] is NOT set!"); + } + } + return $GLOBALS['egw_info']['server'][$name]; + } + + /** + * Flush (delete) whole (instance) cache or application/class specific part of it + * + * @param string $level =self::INSTANCE + * @param string $app =null + */ + static public function flush($level=self::INSTANCE, $app=null) + { + $ret = true; + if (!($provider = self::get_provider($level))) + { + $ret = false; + } + else + { + if (!$provider->flush(self::keys($level, $app))) + { + if ($level == self::INSTANCE) + { + self::generate_instance_key(); + } + else + { + $ret = false; + } + } + } + //error_log(__METHOD__."('$level', '$app') returning ".array2string($ret)); + return $ret; + } + + /** + * Unset instance key, so it get read again and re-read install_id from database + */ + static public function unset_instance_key() + { + self::$instance_key = null; + $GLOBALS['egw_info']['server']['install_id'] = self::get_system_config('install_id', false); + } + + /** + * Key used for instance specific data + * + * @var string + */ + private static $instance_key; + + /** + * Generate a new instance key and by doing so effectivly flushes whole instance cache + * + * @param string $install_id =null default use install_id of current instance + * @return string new key also stored in self::$instance_key + */ + static public function generate_instance_key($install_id=null) + { + if (!isset($install_id)) + { + self::$instance_key = null; + $install_id = self::get_system_config('install_id'); + } + $instance_key = self::INSTANCE.'-'.$install_id.'-'.microtime(true); + self::setTree(__CLASS__, $install_id, $instance_key); + + //error_log(__METHOD__."(install_id='$install_id') returning '".$instance_key."'"); + return $instance_key; + } + + /** + * Get keys array from $level, $app and $location + * + * @param string $level Api\Cache::(TREE|INSTANCE) or instance_id + * @param string $app =null + * @param string $location =null + * @return array + */ + static public function keys($level, $app=null, $location=null) + { + static $tree_key = null; + + switch($level) + { + case self::TREE: + if (!isset($tree_key)) + { + $tree_key = $level.'-'.str_replace(array(':','/','\\'),'-', self::$egw_server_root); + // add charset to key, if not utf-8 (as everything we store depends on charset!) + if (($charset = self::get_system_config('system_charset',false)) && $charset != 'utf-8') + { + $tree_key .= '-'.$charset; + } + } + $level_key = $tree_key; + break; + default: // arbitrary install_id given --> check for current instance + if ($level !== $GLOBALS['egw_info']['server']['install_id']) + { + $level_key = self::getTree(__CLASS__, $level); + if (!isset($level_key)) $level_key = self::generate_instance_key($level); + break; + } + // fall-through for current instance + case self::INSTANCE: + if (!isset(self::$instance_key)) + { + self::$instance_key = self::getTree(__CLASS__, self::get_system_config('install_id')); + //error_log(__METHOD__."('$level',...) instance_key read from tree-cache=".array2string(self::$instance_key)); + if (!isset(self::$instance_key)) self::$instance_key = self::generate_instance_key(); + } + $level_key = self::$instance_key; + break; + } + $keys = array($level_key); + if (isset($app)) + { + $keys[] = $app; + if (isset($location)) $keys[] = $location; + } + return $keys; + } + + /** + * Let everyone know the methods of this class should be used only statically + * + */ + function __construct() + { + throw new egw_exception_wrong_parameter("All methods of class ".__CLASS__." should be called static!"); + } +} + +// setting apc as default provider, if apc_fetch function exists AND further checks in Api\Cache\Apc recommed it +if (is_null(Cache::$default_provider)) +{ + Cache::$default_provider = function_exists('apc_fetch') && Cache\Apc::available() ? + 'EGroupware\Api\Cache\Apc' : 'EGroupware\Api\Cache\Files'; +} + +//error_log('Cache::$default_provider='.array2string(Cache::$default_provider)); diff --git a/phpgwapi/inc/class.egw_cache_apc.inc.php b/api/src/Cache/Apc.php similarity index 95% rename from phpgwapi/inc/class.egw_cache_apc.inc.php rename to api/src/Cache/Apc.php index e83d3431f2..d170bcc596 100644 --- a/phpgwapi/inc/class.egw_cache_apc.inc.php +++ b/api/src/Cache/Apc.php @@ -7,17 +7,19 @@ * @package api * @subpackage cache * @author Ralf Becker - * @copyright (c) 2010-15 by Ralf Becker + * @copyright (c) 2010-16 by Ralf Becker * @version $Id$ */ +namespace EGroupware\Api\Cache; + /** * Caching provider storing data in PHP's APC or APCu extension / shared memory. * * The provider concats all $keys with '::' to get a single string. * * This provider is used by default, if it is available or explicit enabled in your header.inc.php: - * $GLOBALS['egw_info']['server']['cache_provider_instance'] = array('egw_cache_apc'); + * $GLOBALS['egw_info']['server']['cache_provider_instance'] = array('EGroupware\Api\Cache\Apc'); * and optional also $GLOBALS['egw_info']['server']['cache_provider_tree'] (defaults to instance) * * APC(u) and CLI: @@ -25,14 +27,14 @@ * APC(u) is not enabled by default for CLI (apc.enable_cli), nor would it access same shared memory! * It makes no sense to fall back to files cache, as this is probably quite outdated, * if PHP via Webserver uses APC. Better to use no cache at all. - * egw_cache::get*() will return NULL for not found and egw_cache::[un]set*() + * Api\Cache::get*() will return NULL for not found and Api\Cache::[un]set*() * false for not being able to (un)set anything. * It also does not make sense to report failure by throwing an Exception and filling * up cron logs. * --> if APC(u) is available for Webserver, we report availability for CLI too, * but use no cache at all! */ -class egw_cache_apc extends egw_cache_provider_check implements egw_cache_provider +class Apc extends Base implements Provider { /** * Constructor, eg. opens the connection to the backend diff --git a/api/src/Cache/Base.php b/api/src/Cache/Base.php new file mode 100644 index 0000000000..83f2b5e0c4 --- /dev/null +++ b/api/src/Cache/Base.php @@ -0,0 +1,206 @@ + + * @copyright (c) 2009-16 by Ralf Becker + * @version $Id$ + */ + +namespace EGroupware\Api\Cache; + +use EGroupware\Api; + +/** + * Base class for all caching providers + * + * Implements some checks used for testing providers. + */ +abstract class Base implements Provider +{ + /** + * Run several checks on a caching provider + * + * @param boolean $verbose =false true: echo failed checks + * @return int number of failed checks + */ + function check($verbose=false) + { + // set us up as provider for Api\Cache class + $GLOBALS['egw_info']['server']['install_id'] = md5(microtime(true)); + unset($GLOBALS['egw_info']['server']['cache_provider_instance']); + unset($GLOBALS['egw_info']['server']['cache_provider_tree']); + Api\Cache::$default_provider = get_class($this); + + $failed = 0; + foreach(array( + Api\Cache::TREE => 'tree', + Api\Cache::INSTANCE => 'instance', + ) as $level => $label) + { + $locations = array(); + foreach(array('string',123,true,false,null,array(),array(1,2,3)) as $data) + { + $location = md5(microtime(true).$label.serialize($data)); + $get_before_set = $this->get(array($level,__CLASS__,$location)); + if (!is_null($get_before_set)) + { + if ($verbose) echo "$label: get_before_set=".array2string($get_before_set)." != NULL\n"; + ++$failed; + } + if (($set = $this->set(array($level,__CLASS__,$location), $data, 10)) !== true) + { + if ($verbose) echo "$label: set returned ".array2string($set)." !== TRUE\n"; + ++$failed; + } + $get_after_set = $this->get(array($level,__CLASS__,$location)); + if ($get_after_set !== $data) + { + if ($verbose) echo "$label: get_after_set=".array2string($get_after_set)." !== ".array2string($data)."\n"; + ++$failed; + } + if (is_a($this, 'EGroupware\Api\Cache\ProviderMultiple')) + { + $mget_after_set = $this->mget(array($level,__CLASS__,array($location))); + if ($mget_after_set[$location] !== $data) + { + if ($verbose) echo "$label: mget_after_set['$location']=".array2string($mget_after_set[$location])." !== ".array2string($data)."\n"; + ++$failed; + } + } + $add_after_set = $this->add(array($level,__CLASS__,$location), 'other-data'); + if ($add_after_set !== false) + { + if ($verbose) echo "$label: add_after_set=".array2string($add_after_set)."\n"; + ++$failed; + } + if (($delete = $this->delete(array($level,__CLASS__,$location))) !== true) + { + if ($verbose) echo "$label: delete returned ".array2string($delete)." !== TRUE\n"; + ++$failed; + } + $get_after_delete = $this->get(array($level,__CLASS__,$location)); + if (!is_null($get_after_delete)) + { + if ($verbose) echo "$label: get_after_delete=".array2string($get_after_delete)." != NULL\n"; + ++$failed; + } + // prepare for mget of everything + if (is_a($this, 'EGroupware\Api\Cache\ProviderMultiple')) + { + $locations[$location] = $data; + $mget_after_delete = $this->mget(array($level,__CLASS__,array($location))); + if (isset($mget_after_delete[$location])) + { + if ($verbose) echo "$label: mget_after_delete['$location']=".array2string($mget_after_delete[$location])." != NULL\n"; + ++$failed; + } + } + elseif (!is_null($data)) // emulation can NOT distinquish between null and not set + { + $locations[$location] = $data; + } + $add_after_delete = $this->add(array($level,__CLASS__,$location), $data, 10); + if ($add_after_delete !== true) + { + if ($verbose) echo "$label: add_after_delete=".array2string($add_after_delete)."\n"; + ++$failed; + } + else + { + $get_after_add = $this->get(array($level,__CLASS__,$location)); + if ($get_after_add !== $data) + { + if ($verbose) echo "$label: get_after_add=".array2string($get_after_add)." !== ".array2string($data)."\n"; + ++$failed; + } + } + } + // get all above in one request + $keys = array_keys($locations); + $keys_bogus = array_merge(array('not-set'),array_keys($locations),array('not-set-too')); + if (is_a($this, 'EGroupware\Api\Cache\ProviderMultiple')) + { + $mget = $this->mget(array($level,__CLASS__,$keys)); + $mget_bogus = $this->mget(array($level,__CLASS__,$keys_bogus)); + /* Api\Cache::getCache() gives a different result, as it does NOT use $level direkt + } + else + { + $mget = Api\Cache::getCache($level, __CLASS__, $keys); + $mget_bogus = Api\Cache::getCache($level, __CLASS__, $keys_bogus); + }*/ + if ($mget !== $locations) + { + if ($verbose) echo "$label: mget=\n".array2string($mget)." !==\n".array2string($locations)."\n"; + ++$failed; + } + if ($mget_bogus !== $locations) + { + if ($verbose) echo "$label: mget(".array2string($keys_bogus).")=\n".array2string($mget_bogus)." !==\n".array2string($locations)."\n"; + ++$failed; + } + } + } + + return $failed; + } + + /** + * Delete all data under given keys + * + * Providers can return false, if they do not support flushing part of the cache (eg. memcache) + * + * @param array $keys eg. array($level,$app,$location) + * @return boolean true on success, false on error (eg. $key not set) + */ + function flush(array $keys) + { + unset($keys); // required by function signature + return false; + } +} + +// some testcode, if this file is called via it's URL +// can be run on command-line: sudo php -d apc.enable_cli=1 -f api/src/Cache/Base.php +/*if (isset($_SERVER['SCRIPT_FILENAME']) && realpath($_SERVER['SCRIPT_FILENAME']) == __FILE__) +{ + if (!isset($_SERVER['HTTP_HOST'])) + { + chdir(dirname(__FILE__)); // to enable our relative pathes to work + } + $GLOBALS['egw_info'] = array( + 'flags' => array( + 'noapi' => true, + ), + ); + include_once '../../../header.inc.php'; + + if (isset($_SERVER['HTTP_HOST'])) echo "
\n";
+
+	foreach(array(
+		'EGroupware\Api\Cache\Apc' => array(),
+		'EGroupware\Api\Cache\Memcache' => array('localhost'),
+		'EGroupware\Api\Cache\Files' => array('/tmp'),
+	) as $class => $param)
+	{
+		echo "Checking $class:\n";
+		try {
+			$start = microtime(true);
+			$provider = new $class($param);
+			$n = 100;
+			for($i=1; $i <= $n; ++$i)
+			{
+				$failed = $provider->check($i == 1);
+			}
+			printf("$failed checks failed, $n iterations took %5.3f sec\n\n", microtime(true)-$start);
+		}
+		catch (Exception $e) {
+			printf($e->getMessage()."\n\n");
+		}
+	}
+}*/
diff --git a/phpgwapi/inc/class.egw_cache_files.inc.php b/api/src/Cache/Files.php
similarity index 95%
rename from phpgwapi/inc/class.egw_cache_files.inc.php
rename to api/src/Cache/Files.php
index fc05905c04..6d4f075c5c 100644
--- a/phpgwapi/inc/class.egw_cache_files.inc.php
+++ b/api/src/Cache/Files.php
@@ -7,17 +7,21 @@
  * @package api
  * @subpackage cache
  * @author Ralf Becker 
- * @copyright (c) 2009-15 by Ralf Becker 
+ * @copyright (c) 2009-16 by Ralf Becker 
  * @version $Id$
  */
 
+namespace EGroupware\Api\Cache;
+
+use EGroupware\Api;
+
 /**
  * Caching provider storing data in files
  *
  * The provider creates subdirs under a given path
  * for each values in $key
  */
-class egw_cache_files extends egw_cache_provider_check implements egw_cache_provider
+class Files extends Base implements Provider
 {
 	/**
 	 * Extension of file used to store expiration > 0
@@ -45,7 +49,7 @@ class egw_cache_files extends egw_cache_provider_check implements egw_cache_prov
 		}
 		else
 		{
-			$this->base_path = egw_cache::get_system_config('temp_dir', false);
+			$this->base_path = Api\Cache::get_system_config('temp_dir', false);
 			if (isset($this->base_path)) $this->base_path .= '/egw_cache';
 		}
 		if (!isset($this->base_path) || !file_exists($this->base_path) && !mkdir($this->base_path,0700,true))
diff --git a/phpgwapi/inc/class.egw_cache_memcache.inc.php b/api/src/Cache/Memcache.php
similarity index 95%
rename from phpgwapi/inc/class.egw_cache_memcache.inc.php
rename to api/src/Cache/Memcache.php
index dcdfd92043..7c66217155 100644
--- a/phpgwapi/inc/class.egw_cache_memcache.inc.php
+++ b/api/src/Cache/Memcache.php
@@ -7,24 +7,26 @@
  * @package api
  * @subpackage cache
  * @author Ralf Becker 
- * @copyright (c) 2009-15 by Ralf Becker 
+ * @copyright (c) 2009-16 by Ralf Becker 
  * @version $Id$
  */
 
+namespace EGroupware\Api\Cache;
+
 /**
  * Caching provider storing data in memcached via PHP's memcache extension
  *
  * The provider concats all $keys with '::' to get a single string.
  *
  * To use this provider set in your header.inc.php:
- * $GLOBALS['egw_info']['server']['cache_provider_instance'] = array('egw_cache_memcache','localhost'[,'otherhost:port']);
+ * $GLOBALS['egw_info']['server']['cache_provider_instance'] = array('EGroupware\Api\Cache\Memcache','localhost'[,'otherhost:port']);
  * and optional also $GLOBALS['egw_info']['server']['cache_provider_tree'] (defaults to instance)
  *
  * You can set more then one server and specify a port, if it's not the default one 11211.
  *
  * If igbinary extension is available, it is prefered over PHP (un)serialize.
  */
-class egw_cache_memcache extends egw_cache_provider_check implements egw_cache_provider_multiple
+class Memcache extends Base implements ProviderMultiple
 {
 	/**
 	 * Instance of Memcache
diff --git a/phpgwapi/inc/class.egw_cache_memcached.inc.php b/api/src/Cache/Memcached.php
similarity index 97%
rename from phpgwapi/inc/class.egw_cache_memcached.inc.php
rename to api/src/Cache/Memcached.php
index 49dec8076e..df77ac1411 100644
--- a/phpgwapi/inc/class.egw_cache_memcached.inc.php
+++ b/api/src/Cache/Memcached.php
@@ -7,17 +7,19 @@
  * @package api
  * @subpackage cache
  * @author Ralf Becker 
- * @copyright (c) 2009-15 by Ralf Becker 
+ * @copyright (c) 2009-16 by Ralf Becker 
  * @version $Id$
  */
 
+namespace EGroupware\Api\Cache;
+
 /**
  * Caching provider storing data in memcached via PHP's memcached extension
  *
  * The provider concats all $keys with '::' to get a single string.
  *
  * To use this provider set in your header.inc.php:
- * $GLOBALS['egw_info']['server']['cache_provider_instance'] = array('egw_cache_memcached','localhost'[,'otherhost:port']);
+ * $GLOBALS['egw_info']['server']['cache_provider_instance'] = array('EGroupware\Api\Cache\Memcached','localhost'[,'otherhost:port']);
  * and optional also $GLOBALS['egw_info']['server']['cache_provider_tree'] (defaults to instance)
  *
  * You can set more then one server and specify a port, if it's not the default one 11211.
@@ -26,7 +28,7 @@
  *
  * If igbinary extension is available, it is prefered over PHP (un)serialize.
  */
-class egw_cache_memcached extends egw_cache_provider_check implements egw_cache_provider_multiple
+class Memcached extends Base implements ProviderMultiple
 {
 	/**
 	 * Instance of Memcached
diff --git a/api/src/Cache/Provider.php b/api/src/Cache/Provider.php
new file mode 100644
index 0000000000..77a7ceb6cb
--- /dev/null
+++ b/api/src/Cache/Provider.php
@@ -0,0 +1,78 @@
+
+ * @copyright (c) 2009-16 by Ralf Becker 
+ * @version $Id$
+ */
+
+namespace EGroupware\Api\Cache;
+
+/**
+ * Interface for a caching provider for tree and instance level
+ *
+ * The provider can eg. create subdirs under /tmp for each key
+ * to store data as a file or concat them with a separator to
+ * get a single string key to eg. store data in memcached
+ */
+interface Provider
+{
+	/**
+	 * Constructor, eg. opens the connection to the backend
+	 *
+	 * @throws Exception if connection to backend could not be established
+	 * @param array $params eg. array(host,port) or array(directory) depending on the provider
+	 */
+	function __construct(array $params);
+
+	/**
+	 * Stores some data in the cache, if it does NOT already exists there
+	 *
+	 * @param array $keys eg. array($level,$app,$location)
+	 * @param mixed $data
+	 * @param int $expiration =0
+	 * @return boolean true on success, false on error, incl. key already exists in cache
+	 */
+	function add(array $keys,$data,$expiration=0);
+
+	/**
+	 * Stores some data in the cache
+	 *
+	 * @param array $keys eg. array($level,$app,$location)
+	 * @param mixed $data
+	 * @param int $expiration =0
+	 * @return boolean true on success, false on error
+	 */
+	function set(array $keys,$data,$expiration=0);
+
+	/**
+	 * Get some data from the cache
+	 *
+	 * @param array $keys eg. array($level,$app,$location)
+	 * @return mixed data stored or NULL if not found in cache
+	 */
+	function get(array $keys);
+
+	/**
+	 * Delete some data from the cache
+	 *
+	 * @param array $keys eg. array($level,$app,$location)
+	 * @return boolean true on success, false on error (eg. $key not set)
+	 */
+	function delete(array $keys);
+
+	/**
+	 * Delete all data under given keys
+	 *
+	 * Providers can return false, if they do not support flushing part of the cache (eg. memcache)
+	 *
+	 * @param array $keys eg. array($level,$app,$location)
+	 * @return boolean true on success, false on error (eg. $key not set)
+	 */
+	function flush(array $keys);
+}
diff --git a/api/src/Cache/ProviderMultiple.php b/api/src/Cache/ProviderMultiple.php
new file mode 100644
index 0000000000..29675d8c9a
--- /dev/null
+++ b/api/src/Cache/ProviderMultiple.php
@@ -0,0 +1,28 @@
+
+ * @copyright (c) 2009-16 by Ralf Becker 
+ * @version $Id$
+ */
+
+namespace EGroupware\Api\Cache;
+
+/**
+ * Interface for a caching provider being able to retrieve multiple entires
+ */
+interface ProviderMultiple
+{
+	/**
+	 * Get multiple data from the cache
+	 *
+	 * @param array $keys eg. array of array($level,$app,array $locations)
+	 * @return array key => data stored, not found keys are NOT returned
+	 */
+	function mget(array $keys);
+}
diff --git a/api/src/Db.php b/api/src/Db.php
index 11963a7e57..03dfc95ad6 100644
--- a/api/src/Db.php
+++ b/api/src/Db.php
@@ -13,7 +13,6 @@
 
 namespace EGroupware\Api;
 
-use egw_cache;
 use egw_exception_db_connection;
 use egw_exception_db_invalid_sql;
 use egw_exception_wrong_parameter;
@@ -406,7 +405,7 @@ class Db
 	{
 		$hosts = explode(';', $this->Host[0] == '@' ? getenv(substr($this->Host, 1)) : $this->Host);
 		$num_hosts = count($hosts);
-		$n =& egw_cache::getSession(__CLASS__, $this->Host);
+		$n =& Cache::getSession(__CLASS__, $this->Host);
 		if (!isset($n)) $n = 0;
 
 		if ($next && ++$n >= $num_hosts+2)
diff --git a/api/src/Db/Backup.php b/api/src/Db/Backup.php
index ca784bfe31..e070ab0263 100644
--- a/api/src/Db/Backup.php
+++ b/api/src/Db/Backup.php
@@ -14,8 +14,8 @@
 namespace EGroupware\Api\Db;
 
 use EGroupware\Api;
+
 use egw_exception_db_invalid_sql;
-use egw_cache;
 use config;
 use translation;
 use html;
@@ -493,9 +493,9 @@ class Backup
 				return lang('Restore failed');
 			}
 		}
-		// generate an install_id if we dont have one (it breaks egw_cache::flush() stalling the upgrade)
+		// generate an install_id if we dont have one (it breaks Api\Cache::flush() stalling the upgrade)
 		unset($GLOBALS['egw_info']['server']['install_id']);
-		if (!($GLOBALS['egw_info']['server']['install_id'] = egw_cache::get_system_config('install_id', false)))
+		if (!($GLOBALS['egw_info']['server']['install_id'] = Api\Cache::get_system_config('install_id', false)))
 		{
 			$GLOBALS['egw_info']['server']['install_id'] = md5(microtime(true).$_SERVER['HTTP_HOST']);
 			$this->db->insert('egw_config', array(
@@ -506,7 +506,7 @@ class Backup
 			), __LINE__, __FILE__);
 		}
 		// flush instance cache
-		egw_cache::flush(egw_cache::INSTANCE);
+		Api\Cache::flush(Api\Cache::INSTANCE);
 
 		// search-and-register-hooks
 		$GLOBALS['egw']->hooks->register_all_hooks();
diff --git a/api/src/Vfs.php b/api/src/Vfs.php
index 8c139811e4..09980cc09b 100644
--- a/api/src/Vfs.php
+++ b/api/src/Vfs.php
@@ -27,7 +27,6 @@ use egw_exception_wrong_parameter;
 use egw_exception_wrong_userinput;
 use egw_exception;
 use egw_time;
-use egw_cache;
 
 /**
  * Class containing static methods to use the new eGW virtual file system
@@ -987,7 +986,7 @@ class Vfs extends Vfs\StreamWrapper
 	{
 		if ($session_only)
 		{
-			$session_eacls =& egw_cache::getSession(__CLASS__, self::SESSION_EACL);
+			$session_eacls =& Cache::getSession(__CLASS__, self::SESSION_EACL);
 			$session_eacls[] = array(
 				'path'   => $url[0] == '/' ? $url : self::parse_url($url, PHP_URL_PATH),
 				'owner'  => $owner ? $owner : self::$user,
@@ -1010,7 +1009,7 @@ class Vfs extends Vfs\StreamWrapper
 	{
 		$eacls = self::_call_on_backend('get_eacl',array($path),true);	// true = fail silent (no PHP Warning)
 
-		$session_eacls =& egw_cache::getSession(__CLASS__, self::SESSION_EACL);
+		$session_eacls =& Cache::getSession(__CLASS__, self::SESSION_EACL);
 		if ($session_eacls)
 		{
 			// eacl is recursive, therefore we have to match all parent-dirs too
diff --git a/api/src/Vfs/Sqlfs/StreamWrapper.php b/api/src/Vfs/Sqlfs/StreamWrapper.php
index 5c16198bfb..dce5bec684 100644
--- a/api/src/Vfs/Sqlfs/StreamWrapper.php
+++ b/api/src/Vfs/Sqlfs/StreamWrapper.php
@@ -7,19 +7,18 @@
  * @package api
  * @subpackage vfs
  * @author Ralf Becker 
- * @copyright (c) 2008-15 by Ralf Becker 
+ * @copyright (c) 2008-16 by Ralf Becker 
  * @version $Id$
  */
 
 namespace EGroupware\Api\Vfs\Sqlfs;
 
 use EGroupware\Api\Vfs;
+use EGroupware\Api;
 
 // explicitly import old phpgwapi classes used:
-use egw_cache;
 use mime_magic;
 use config;
-use translation;
 use egw_exception_db;
 use egw_exception_wrong_parameter;
 use egw_exception_assertion_failed;
@@ -186,7 +185,7 @@ class StreamWrapper implements Vfs\StreamWrapperIface
 
 		self::$stat_cache = array();
 
-		egw_cache::setSession(self::EACL_APPNAME, 'extended_acl', self::$extended_acl = null);
+		Api\Cache::setSession(self::EACL_APPNAME, 'extended_acl', self::$extended_acl = null);
 	}
 
 	/**
@@ -1377,7 +1376,7 @@ class StreamWrapper implements Vfs\StreamWrapperIface
 	 */
 	static protected function _read_extended_acl()
 	{
-		if ((self::$extended_acl = egw_cache::getSession(self::EACL_APPNAME, 'extended_acl')))
+		if ((self::$extended_acl = Api\Cache::getSession(self::EACL_APPNAME, 'extended_acl')))
 		{
 			return;		// ext. ACL read from session.
 		}
@@ -1398,7 +1397,7 @@ class StreamWrapper implements Vfs\StreamWrapperIface
 		uksort(self::$extended_acl, function($a,$b) {
 			return strlen($b)-strlen($a);
 		});
-		egw_cache::setSession(self::EACL_APPNAME, 'extended_acl', self::$extended_acl);
+		Api\Cache::setSession(self::EACL_APPNAME, 'extended_acl', self::$extended_acl);
 		if (self::LOG_LEVEL > 1) error_log(__METHOD__.'() '.array2string(self::$extended_acl));
 	}
 
@@ -1464,7 +1463,7 @@ class StreamWrapper implements Vfs\StreamWrapperIface
 		}
 		if ($ret)
 		{
-			egw_cache::setSession(self::EACL_APPNAME, 'extended_acl', self::$extended_acl);
+			Api\Cache::setSession(self::EACL_APPNAME, 'extended_acl', self::$extended_acl);
 		}
 		if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path,$rights,$owner,$fs_id)=".(int)$ret);
 		return $ret;
diff --git a/phpgwapi/inc/class.egw_cache.inc.php b/phpgwapi/inc/class.egw_cache.inc.php
index 9f610f2145..54c806b3bb 100644
--- a/phpgwapi/inc/class.egw_cache.inc.php
+++ b/phpgwapi/inc/class.egw_cache.inc.php
@@ -7,996 +7,20 @@
  * @package api
  * @subpackage cache
  * @author Ralf Becker 
- * @copyright (c) 2009-15 by Ralf Becker 
+ * @copyright (c) 2009-16 by Ralf Becker 
  * @version $Id$
  */
 
+use EGroupware\Api;
+
 /**
  * Class to manage caching in eGroupware.
  *
- * It allows to cache on 4 levels:
- * a) tree:     for all instances/domains runining on a certain source path
- * b) instance: for all sessions on a given instance
- * c) session:  for all requests of a session, same as egw_session::appsession()
- * d) request:  just for this request (same as using a static variable)
- *
- * There's a get, a set and a unset method for each level: eg. getTree() or setInstance(),
- * as well as a variant allowing to specify the level as first parameter: eg. unsetCache()
- *
- * getXXX($app,$location,$callback=null,array $callback_params,$expiration=0)
- * has three optional parameters allowing to specify:
- * 3. a callback if requested data is not yes stored. In that case the callback is called
- *    and it's value is stored in the cache AND retured
- * 4. parameters to pass to the callback as array, see call_user_func_array
- * 5. an expiration time in seconds to specify how long data should be cached,
- *    default 0 means infinit (this time is not garantied and not supported for all levels!)
- *
- * Data is stored under an application name and a location, like egw_session::appsession().
- * In fact data stored at cache level egw_cache::SESSION, is stored in the same way as
- * egw_session::appsession() so both methods can be used with each other.
- *
- * The $app parameter should be either the app or the class name, which both are unique.
- *
- * The tree and instance wide cache uses a certain provider class, to store the data
- * eg. in memcached or if there's nothing else configured in the filesystem (eGW's temp_dir).
- *
- * "Admin >> clear cache and register hooks" allways only clears instance level cache of
- * calling instance. It never clears tree level cache, which makes it important to set
- * resonable expiry times or think about an other means of clearing that particular item.
- * (Not clearing of tree-level cache is important, as regenerating it is an expensive
- * operation for a huge scale EGroupware hosting operation.)
- *
- * Apps needing to talk to multiple EGroupware instances (eg. Stylite Managementserver)
- * can use install_id of instance as $level parameter to (set|get|unset)Cache method.
+ * @deprecated use Api\Cache instead
  */
-class egw_cache
-{
-	/**
-	 * tree-wide storage
-	 */
-	const TREE = 'Tree';
-	/**
-	 * instance-wide storage
-	 */
-	const INSTANCE = 'Instance';
-	/**
-	 * session-wide storage
-	 */
-	const SESSION = 'Session';
-	/**
-	 * request-wide storage
-	 */
-	const REQUEST = 'Request';
+class egw_cache extends Api\Cache {}
 
-	/**
-	 * Default provider for tree and instance data
-	 *
-	 * Can be specified eg. in the header.inc.php by setting:
-	 * $GLOBALS['egw_info']['server']['cache_provider_instance'] and optional
-	 * $GLOBALS['egw_info']['server']['cache_provider_tree'] (defaults to instance)
-	 *
-	 * Default is set (if not set here) after class definition to egw_cache_apc or egw_cache_files,
-	 * depending on function 'apc_fetch' exists or not
-	 *
-	 * @var array
-	 */
-	static $default_provider;	// = array('egw_cache_files');// array('egw_cache_memcache','localhost');
-
-	/**
-	 * Maximum expiration time, if set unlimited expiration (=0) or bigger expiration times are replaced with that time
-	 *
-	 * @var int
-	 */
-	static $max_expiration;
-
-	/**
-	 * Used to determine keys for tree- and instance-level caches
-	 *
-	 * @var string
-	 */
-	static $egw_server_root = EGW_SERVER_ROOT;
-
-	/**
-	 * Add some data in the cache, only if the key does not yet exist
-	 *
-	 * @param string $level use egw_cache::(TREE|INSTANCE)
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @param mixed $data
-	 * @param int $expiration =0 expiration time in seconds, default 0 = never
-	 * @return boolean true if data could be stored, false otherwise incl. key already existed
-	 */
-	static public function addCache($level,$app,$location,$data,$expiration=0)
-	{
-		//error_log(__METHOD__."('$level','$app','$location',".array2string($data).",$expiration)");
-		switch($level)
-		{
-			case self::SESSION:
-			case self::REQUEST:
-				throw new egw_exception_wrong_parameter(__METHOD__."('$level', ...) unsupported level parameter!");
-
-			case self::INSTANCE:
-			case self::TREE:
-			default:
-				if (!($provider = self::get_provider($level)))
-				{
-					return false;
-				}
-				// limit expiration to configured maximum time
-				if (isset(self::$max_expiration) && (!$expiration || $expiration > self::$max_expiration))
-				{
-					$expiration = self::$max_expiration;
-				}
-				return $provider->add(self::keys($level,$app,$location),$data,$expiration);
-		}
-		throw new egw_exception_wrong_parameter(__METHOD__."() unknown level '$level'!");
-	}
-
-	/**
-	 * Set some data in the cache
-	 *
-	 * @param string $level use egw_cache::(TREE|INSTANCE|SESSION|REQUEST)
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @param mixed $data
-	 * @param int $expiration =0 expiration time in seconds, default 0 = never
-	 * @return boolean true if data could be stored, false otherwise
-	 */
-	static public function setCache($level,$app,$location,$data,$expiration=0)
-	{
-		//error_log(__METHOD__."('$level','$app','$location',".array2string($data).",$expiration)");
-		switch($level)
-		{
-			case self::SESSION:
-			case self::REQUEST:
-				return call_user_func(array(__CLASS__,'set'.$level),$app,$location,$data,$expiration);
-
-			case self::INSTANCE:
-			case self::TREE:
-			default:
-				if (!($provider = self::get_provider($level)))
-				{
-					return false;
-				}
-				// limit expiration to configured maximum time
-				if (isset(self::$max_expiration) && (!$expiration || $expiration > self::$max_expiration))
-				{
-					$expiration = self::$max_expiration;
-				}
-				return $provider->set(self::keys($level,$app,$location),$data,$expiration);
-		}
-		throw new egw_exception_wrong_parameter(__METHOD__."() unknown level '$level'!");
-	}
-
-	/**
-	 * Get some data from the cache
-	 *
-	 * @param string $level use egw_cache::(TREE|INSTANCE|SESSION|REQUEST)
-	 * @param string $app application storing data
-	 * @param string|array $location location(s) name for data
-	 * @param callback $callback =null callback to get/create the value, if it's not cache
-	 * @param array $callback_params =array() array with parameters for the callback
-	 * @param int $expiration =0 expiration time in seconds, default 0 = never
-	 * @return mixed NULL if data not found in cache (and no callback specified) or
-	 * 	if $location is an array: location => data pairs for existing location-data, non-existing is not returned
-	 */
-	static public function getCache($level,$app,$location,$callback=null,array $callback_params=array(),$expiration=0)
-	{
-		switch($level)
-		{
-			case self::SESSION:
-			case self::REQUEST:
-				foreach((array)$location as $l)
-				{
-					$data[$l] = call_user_func(array(__CLASS__,'get'.$level),$app,$l,$callback,$callback_params,$expiration);
-				}
-				return is_array($location) ? $data : $data[$l];
-
-			case self::INSTANCE:
-			case self::TREE:
-			default:
-				if (!($provider = self::get_provider($level)))
-				{
-					return null;
-				}
-				try {
-					if (is_array($location))
-					{
-						if (!is_null($callback))
-						{
-							throw new egw_exception_wrong_parameter(__METHOD__."() you can NOT use multiple locations (\$location parameter is an array) together with a callback!");
-						}
-						if (is_a($provider, 'egw_cache_provider_multiple'))
-						{
-							$data = $provider->mget($keys=self::keys($level,$app,$location));
-						}
-						else	// default implementation calls get multiple times
-						{
-							$data = array();
-							foreach($location as $l)
-							{
-								$data[$l] = $provider->get($keys=self::keys($level,$app,$l));
-								if (!isset($data[$l])) unset($data[$l]);
-							}
-						}
-					}
-					else
-					{
-						$data = $provider->get($keys=self::keys($level,$app,$location));
-						if (is_null($data) && !is_null($callback))
-						{
-							$data = call_user_func_array($callback,$callback_params);
-							// limit expiration to configured maximum time
-							if (isset(self::$max_expiration) && (!$expiration || $expiration > self::$max_expiration))
-							{
-								$expiration = self::$max_expiration;
-							}
-							$provider->set($keys,$data,$expiration);
-						}
-					}
-				}
-				catch(Exception $e) {
-					unset($e);
-					$data = null;
-				}
-				return $data;
-		}
-		throw new egw_exception_wrong_parameter(__METHOD__."() unknown level '$level'!");
-	}
-
-	/**
-	 * Unset some data in the cache
-	 *
-	 * @param string $level use egw_cache::(TREE|INSTANCE|SESSION|REQUEST)
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @return boolean true if data was set, false if not (like isset())
-	 */
-	static public function unsetCache($level,$app,$location)
-	{
-		switch($level)
-		{
-			case self::SESSION:
-			case self::REQUEST:
-				return call_user_func(array(__CLASS__,'unset'.$level),$app,$location);
-
-			case self::INSTANCE:
-			case self::TREE:
-			default:
-				if (!($provider = self::get_provider($level, false)))
-				{
-					return false;
-				}
-				return $provider->delete(self::keys($level,$app,$location));
-		}
-		throw new egw_exception_wrong_parameter(__METHOD__."() unknown level '$level'!");
-	}
-
-	/**
-	 * Set some data in the cache for the whole source tree (all instances)
-	 *
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @param mixed $data
-	 * @param int $expiration =0 expiration time in seconds, default 0 = never
-	 * @return boolean true if data could be stored, false otherwise
-	 */
-	static public function setTree($app,$location,$data,$expiration=0)
-	{
-		//error_log(__METHOD__."('$app','$location',".array2string($data).",$expiration)");
-		return self::setCache(self::TREE,$app,$location,$data,$expiration);
-	}
-
-	/**
-	 * Get some data from the cache for the whole source tree (all instances)
-	 *
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @param callback $callback =null callback to get/create the value, if it's not cache
-	 * @param array $callback_params =array() array with parameters for the callback
-	 * @param int $expiration =0 expiration time in seconds, default 0 = never
-	 * @return mixed NULL if data not found in cache (and no callback specified)
-	 */
-	static public function getTree($app,$location,$callback=null,array $callback_params=array(),$expiration=0)
-	{
-		return self::getCache(self::TREE,$app,$location,$callback,$callback_params,$expiration);
-	}
-
-	/**
-	 * Unset some data in the cache for the whole source tree (all instances)
-	 *
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @return boolean true if data was set, false if not (like isset())
-	 */
-	static public function unsetTree($app,$location)
-	{
-		return self::unsetCache(self::TREE,$app,$location);
-	}
-
-	/**
-	 * Set some data in the cache for the whole source tree (all instances)
-	 *
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @param mixed $data
-	 * @param int $expiration =0 expiration time in seconds, default 0 = never
-	 * @return boolean true if data could be stored, false otherwise
-	 */
-	static public function setInstance($app,$location,$data,$expiration=0)
-	{
-		return self::setCache(self::INSTANCE,$app,$location,$data,$expiration);
-	}
-
-	/**
-	 * Get some data from the cache for the whole source tree (all instances)
-	 *
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @param callback $callback =null callback to get/create the value, if it's not cache
-	 * @param array $callback_params =array() array with parameters for the callback
-	 * @param int $expiration =0 expiration time in seconds, default 0 = never
-	 * @return mixed NULL if data not found in cache (and no callback specified)
-	 */
-	static public function getInstance($app,$location,$callback=null,array $callback_params=array(),$expiration=0)
-	{
-		return self::getCache(self::INSTANCE,$app,$location,$callback,$callback_params,$expiration);
-	}
-
-	/**
-	 * Unset some data in the cache for the whole source tree (all instances)
-	 *
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @return boolean true if data was set, false if not (like isset())
-	 */
-	static public function unsetInstance($app,$location)
-	{
-		return self::unsetCache(self::INSTANCE,$app,$location);
-	}
-
-	/**
-	 * Set some data in the cache for the whole source tree (all instances)
-	 *
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @param mixed $data
-	 * @param int $expiration =0 expiration time in seconds, default 0 = never
-	 * @return boolean true if data could be stored, false otherwise
-	 */
-	static public function setSession($app,$location,$data,$expiration=0)
-	{
-		unset($expiration);	// not used, but required by function signature
-		if (isset($_SESSION[egw_session::EGW_SESSION_ENCRYPTED]))
-		{
-			if (egw_session::ERROR_LOG_DEBUG) error_log(__METHOD__.' called after session was encrypted --> ignored!');
-			return false;	// can no longer store something in the session, eg. because commit_session() was called
-		}
-		$_SESSION[egw_session::EGW_APPSESSION_VAR][$app][$location] = $data;
-
-		return true;
-	}
-
-	/**
-	 * Get some data from the cache for the whole source tree (all instances)
-	 *
-	 * Returns a reference to the var in the session!
-	 *
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @param callback $callback =null callback to get/create the value, if it's not cache
-	 * @param array $callback_params =array() array with parameters for the callback
-	 * @param int $expiration =0 expiration time in seconds, default 0 = never
-	 * @return mixed NULL if data not found in cache (and no callback specified)
-	 */
-	static public function &getSession($app,$location,$callback=null,array $callback_params=array(),$expiration=0)
-	{
-		unset($expiration);	// not used, but required by function signature
-		if (isset($_SESSION[egw_session::EGW_SESSION_ENCRYPTED]))
-		{
-			if (egw_session::ERROR_LOG_DEBUG) error_log(__METHOD__.' called after session was encrypted --> ignored!');
-			return null;	// can no longer store something in the session, eg. because commit_session() was called
-		}
-		if (!isset($_SESSION[egw_session::EGW_APPSESSION_VAR][$app][$location]) && !is_null($callback))
-		{
-			$_SESSION[egw_session::EGW_APPSESSION_VAR][$app][$location] = call_user_func_array($callback,$callback_params);
-		}
-		return $_SESSION[egw_session::EGW_APPSESSION_VAR][$app][$location];
-	}
-
-	/**
-	 * Unset some data in the cache for the whole source tree (all instances)
-	 *
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @return boolean true if data was set, false if not (like isset())
-	 */
-	static public function unsetSession($app,$location)
-	{
-		if (isset($_SESSION[egw_session::EGW_SESSION_ENCRYPTED]))
-		{
-			if (egw_session::ERROR_LOG_DEBUG) error_log(__METHOD__.' called after session was encrypted --> ignored!');
-			return false;	// can no longer store something in the session, eg. because commit_session() was called
-		}
-		if (!isset($_SESSION[egw_session::EGW_APPSESSION_VAR][$app][$location]))
-		{
-			return false;
-		}
-		unset($_SESSION[egw_session::EGW_APPSESSION_VAR][$app][$location]);
-
-		return true;
-	}
-
-	/**
-	 * Static varible to cache request wide
-	 *
-	 * @var array
-	 */
-	private static $request_cache = array();
-
-	/**
-	 * Set some data in the cache for the whole source tree (all instances)
-	 *
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @param mixed $data
-	 * @param int $expiration =0 expiration time is NOT used for REQUEST!
-	 * @return boolean true if data could be stored, false otherwise
-	 */
-	static public function setRequest($app,$location,$data,$expiration=0)
-	{
-		unset($expiration);	// not used, but required by function signature
-		self::$request_cache[$app][$location] = $data;
-
-		return true;
-	}
-
-	/**
-	 * Get some data from the cache for the whole source tree (all instances)
-	 *
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @param callback $callback =null callback to get/create the value, if it's not cache
-	 * @param array $callback_params =array() array with parameters for the callback
-	 * @param int $expiration =0 expiration time is NOT used for REQUEST!
-	 * @return mixed NULL if data not found in cache (and no callback specified)
-	 */
-	static public function getRequest($app,$location,$callback=null,array $callback_params=array(),$expiration=0)
-	{
-		unset($expiration);	// not used, but required by function signature
-		if (!isset(self::$request_cache[$app][$location]) && !is_null($callback))
-		{
-			self::$request_cache[$app][$location] = call_user_func_array($callback,$callback_params);
-		}
-		return self::$request_cache[$app][$location];
-	}
-
-	/**
-	 * Unset some data in the cache for the whole source tree (all instances)
-	 *
-	 * @param string $app application storing data
-	 * @param string $location location name for data
-	 * @return boolean true if data was set, false if not (like isset())
-	 */
-	static public function unsetRequest($app,$location)
-	{
-		if (!isset(self::$request_cache[$app][$location]))
-		{
-			return false;
-		}
-		unset(self::$request_cache[$app][$location]);
-
-		return true;
-	}
-
-	/**
-	 * Get a caching provider for tree or instance level
-	 *
-	 * The returned provider already has an opened connection
-	 *
-	 * @param string $level egw_cache::(TREE|INSTANCE) or install_id
-	 * @param boolean $log_not_found =true false do not log if no provider found, used eg. to supress error via unsetCache during installation
-	 * @return egw_cache_provider
-	 */
-	static protected function get_provider($level, $log_not_found=true)
-	{
-		static $providers = array();
-
-		if ($level != self::TREE) $level = self::INSTANCE;
-
-		if (!isset($providers[$level]))
-		{
-			$params = $GLOBALS['egw_info']['server']['cache_provider_'.strtolower($level)];
-			if (!isset($params) && $level == self::INSTANCE && isset(self::$default_provider))
-			{
-				$params = self::$default_provider;
-			}
-			if (!isset($params))
-			{
-				if ($level == self::TREE)	// if no tree level provider use the instance level one
-				{
-					$providers[$level] = self::get_provider(self::INSTANCE);
-				}
-				else
-				{
-					$providers[$level] = false;	// no provider specified
-					$reason = 'no provider specified';
-				}
-			}
-			elseif (!$params)
-			{
-					$providers[$level] = false;	// cache for $level disabled
-					$reason = "cache for $level disabled";
-			}
-			else
-			{
-				if (!is_array($params)) $params = (array)$params;
-
-				$class = array_shift($params);
-				if (!class_exists($class))
-				{
-					$providers[$level] = false;	// provider class not found
-					$reason = "provider $class not found";
-				}
-				else
-				{
-					try
-					{
-						$providers[$level] = new $class($params);
-					}
-					catch(Exception $e)
-					{
-						$providers[$level] = false;	// eg. could not open connection to backend
-						$reason = "error instanciating provider $class: ".$e->getMessage();
-					}
-				}
-			}
-			if (!$providers[$level] && $log_not_found) error_log(__METHOD__."($level) no provider found ($reason)!".function_backtrace());
-		}
-		//error_log(__METHOD__."($level) = ".array2string($providers[$level]).', cache_provider='.array2string($GLOBALS['egw_info']['server']['cache_provider_'.strtolower($level)]));
-		return $providers[$level];
-	}
-
-	/**
-	 * Get a system configuration, even if in setup and it's not read
-	 *
-	 * @param string $name
-	 * @param boolean $throw =true throw an exception, if we can't retriev the value
-	 * @return string|boolean string with config or false if not found and !$throw
-	 */
-	static public function get_system_config($name,$throw=true)
-	{
-		if(!isset($GLOBALS['egw_info']['server'][$name]))
-		{
-			if (isset($GLOBALS['egw_setup']) && isset($GLOBALS['egw_setup']->db) || $GLOBALS['egw']->db)
-			{
-				$db = $GLOBALS['egw']->db ? $GLOBALS['egw']->db : $GLOBALS['egw_setup']->db;
-
-				try {
-					if (($rs = $db->select(config::TABLE,'config_value',array(
-						'config_app'	=> 'phpgwapi',
-						'config_name'	=> $name,
-					),__LINE__,__FILE__)))
-					{
-						$GLOBALS['egw_info']['server'][$name] = $rs->fetchColumn();
-					}
-					else
-					{
-						error_log(__METHOD__."('$name', $throw) config value NOT found!");//.function_backtrace());
-					}
-				}
-				catch(egw_exception_db $e)
-				{
-					if ($throw) error_log(__METHOD__."('$name', $throw) cound NOT query value: ".$e->getMessage());//.function_backtrace());
-				}
-			}
-			if (!$GLOBALS['egw_info']['server'][$name] && $throw)
-			{
-				throw new Exception (__METHOD__."($name) \$GLOBALS['egw_info']['server']['$name'] is NOT set!");
-			}
-		}
-		return $GLOBALS['egw_info']['server'][$name];
-	}
-
-	/**
-	 * Flush (delete) whole (instance) cache or application/class specific part of it
-	 *
-	 * @param string $level =self::INSTANCE
-	 * @param string $app =null
-	 */
-	static public function flush($level=self::INSTANCE, $app=null)
-	{
-		$ret = true;
-		if (!($provider = self::get_provider($level)))
-		{
-			$ret = false;
-		}
-		else
-		{
-			if (!$provider->flush(self::keys($level, $app)))
-			{
-				if ($level == self::INSTANCE)
-				{
-					self::generate_instance_key();
-				}
-				else
-				{
-					$ret = false;
-				}
-			}
-		}
-		//error_log(__METHOD__."('$level', '$app') returning ".array2string($ret));
-		return $ret;
-	}
-
-	/**
-	 * Unset instance key, so it get read again and re-read install_id from database
-	 */
-	static public function unset_instance_key()
-	{
-		self::$instance_key = null;
-		$GLOBALS['egw_info']['server']['install_id'] = egw_cache::get_system_config('install_id', false);
-	}
-
-	/**
-	 * Key used for instance specific data
-	 *
-	 * @var string
-	 */
-	private static $instance_key;
-
-	/**
-	 * Generate a new instance key and by doing so effectivly flushes whole instance cache
-	 *
-	 * @param string $install_id =null default use install_id of current instance
-	 * @return string new key also stored in self::$instance_key
-	 */
-	static public function generate_instance_key($install_id=null)
-	{
-		if (!isset($install_id))
-		{
-			self::$instance_key = null;
-			$install_id = self::get_system_config('install_id');
-		}
-		$instance_key = self::INSTANCE.'-'.$install_id.'-'.microtime(true);
-		self::setTree(__CLASS__, $install_id, $instance_key);
-
-		//error_log(__METHOD__."(install_id='$install_id') returning '".$instance_key."'");
-		return $instance_key;
-	}
-
-	/**
-	 * Get keys array from $level, $app and $location
-	 *
-	 * @param string $level egw_cache::(TREE|INSTANCE) or instance_id
-	 * @param string $app =null
-	 * @param string $location =null
-	 * @return array
-	 */
-	static public function keys($level, $app=null, $location=null)
-	{
-		static $tree_key = null;
-
-		switch($level)
-		{
-			case self::TREE:
-				if (!isset($tree_key))
-				{
-					$tree_key = $level.'-'.str_replace(array(':','/','\\'),'-', self::$egw_server_root);
-					// add charset to key, if not utf-8 (as everything we store depends on charset!)
-					if (($charset = self::get_system_config('system_charset',false)) && $charset != 'utf-8')
-					{
-						$tree_key .= '-'.$charset;
-					}
-				}
-				$level_key = $tree_key;
-				break;
-			default:	// arbitrary install_id given --> check for current instance
-				if ($level !== $GLOBALS['egw_info']['server']['install_id'])
-				{
-					$level_key = self::getTree(__CLASS__, $level);
-					if (!isset($level_key)) $level_key = self::generate_instance_key($level);
-					break;
-				}
-				// fall-through for current instance
-			case self::INSTANCE:
-				if (!isset(self::$instance_key))
-				{
-					self::$instance_key = self::getTree(__CLASS__, self::get_system_config('install_id'));
-					//error_log(__METHOD__."('$level',...) instance_key read from tree-cache=".array2string(self::$instance_key));
-					if (!isset(self::$instance_key)) self::$instance_key = self::generate_instance_key();
-				}
-				$level_key = self::$instance_key;
-				break;
-		}
-		$keys = array($level_key);
-		if (isset($app))
-		{
-			$keys[] = $app;
-			if (isset($location)) $keys[] = $location;
-		}
-		return $keys;
-	}
-
-	/**
-	 * Let everyone know the methods of this class should be used only statically
-	 *
-	 */
-	function __construct()
-	{
-		throw new egw_exception_wrong_parameter("All methods of class ".__CLASS__." should be called static!");
-	}
-}
-
-/**
- * Interface for a caching provider for tree and instance level
- *
- * The provider can eg. create subdirs under /tmp for each key
- * to store data as a file or concat them with a separator to
- * get a single string key to eg. store data in memcached
- */
-interface egw_cache_provider
-{
-	/**
-	 * Constructor, eg. opens the connection to the backend
-	 *
-	 * @throws Exception if connection to backend could not be established
-	 * @param array $params eg. array(host,port) or array(directory) depending on the provider
-	 */
-	function __construct(array $params);
-
-	/**
-	 * Stores some data in the cache, if it does NOT already exists there
-	 *
-	 * @param array $keys eg. array($level,$app,$location)
-	 * @param mixed $data
-	 * @param int $expiration =0
-	 * @return boolean true on success, false on error, incl. key already exists in cache
-	 */
-	function add(array $keys,$data,$expiration=0);
-
-	/**
-	 * Stores some data in the cache
-	 *
-	 * @param array $keys eg. array($level,$app,$location)
-	 * @param mixed $data
-	 * @param int $expiration =0
-	 * @return boolean true on success, false on error
-	 */
-	function set(array $keys,$data,$expiration=0);
-
-	/**
-	 * Get some data from the cache
-	 *
-	 * @param array $keys eg. array($level,$app,$location)
-	 * @return mixed data stored or NULL if not found in cache
-	 */
-	function get(array $keys);
-
-	/**
-	 * Delete some data from the cache
-	 *
-	 * @param array $keys eg. array($level,$app,$location)
-	 * @return boolean true on success, false on error (eg. $key not set)
-	 */
-	function delete(array $keys);
-
-	/**
-	 * Delete all data under given keys
-	 *
-	 * Providers can return false, if they do not support flushing part of the cache (eg. memcache)
-	 *
-	 * @param array $keys eg. array($level,$app,$location)
-	 * @return boolean true on success, false on error (eg. $key not set)
-	 */
-	function flush(array $keys);
-}
-
-/**
- * Interface for a caching provider for tree and instance level
- *
- * The provider can eg. create subdirs under /tmp for each key
- * to store data as a file or concat them with a separator to
- * get a single string key to eg. store data in memcached
- */
-interface egw_cache_provider_multiple
-{
-	/**
-	 * Get multiple data from the cache
-	 *
-	 * @param array $keys eg. array of array($level,$app,array $locations)
-	 * @return array key => data stored, not found keys are NOT returned
-	 */
-	function mget(array $keys);
-}
-
-abstract class egw_cache_provider_check implements egw_cache_provider
-{
-	/**
-	 * Run several checks on a caching provider
-	 *
-	 * @param boolean $verbose =false true: echo failed checks
-	 * @return int number of failed checks
-	 */
-	function check($verbose=false)
-	{
-		// set us up as provider for egw_cache class
-		$GLOBALS['egw_info']['server']['install_id'] = md5(microtime(true));
-		unset($GLOBALS['egw_info']['server']['cache_provider_instance']);
-		unset($GLOBALS['egw_info']['server']['cache_provider_tree']);
-		egw_cache::$default_provider = get_class($this);
-
-		$failed = 0;
-		foreach(array(
-			egw_cache::TREE => 'tree',
-			egw_cache::INSTANCE => 'instance',
-		) as $level => $label)
-		{
-			$locations = array();
-			foreach(array('string',123,true,false,null,array(),array(1,2,3)) as $data)
-			{
-				$location = md5(microtime(true).$label.serialize($data));
-				$get_before_set = $this->get(array($level,__CLASS__,$location));
-				if (!is_null($get_before_set))
-				{
-					if ($verbose) echo "$label: get_before_set=".array2string($get_before_set)." != NULL\n";
-					++$failed;
-				}
-				if (($set = $this->set(array($level,__CLASS__,$location), $data, 10)) !== true)
-				{
-					if ($verbose) echo "$label: set returned ".array2string($set)." !== TRUE\n";
-					++$failed;
-				}
-				$get_after_set = $this->get(array($level,__CLASS__,$location));
-				if ($get_after_set !== $data)
-				{
-					if ($verbose) echo "$label: get_after_set=".array2string($get_after_set)." !== ".array2string($data)."\n";
-					++$failed;
-				}
-				if (is_a($this, 'egw_cache_provider_multiple'))
-				{
-					$mget_after_set = $this->mget(array($level,__CLASS__,array($location)));
-					if ($mget_after_set[$location] !== $data)
-					{
-						if ($verbose) echo "$label: mget_after_set['$location']=".array2string($mget_after_set[$location])." !== ".array2string($data)."\n";
-						++$failed;
-					}
-				}
-				$add_after_set = $this->add(array($level,__CLASS__,$location), 'other-data');
-				if ($add_after_set !== false)
-				{
-					if ($verbose) echo "$label: add_after_set=".array2string($add_after_set)."\n";
-					++$failed;
-				}
-				if (($delete = $this->delete(array($level,__CLASS__,$location))) !== true)
-				{
-					if ($verbose) echo "$label: delete returned ".array2string($delete)." !== TRUE\n";
-					++$failed;
-				}
-				$get_after_delete = $this->get(array($level,__CLASS__,$location));
-				if (!is_null($get_after_delete))
-				{
-					if ($verbose) echo "$label: get_after_delete=".array2string($get_after_delete)." != NULL\n";
-					++$failed;
-				}
-				// prepare for mget of everything
-				if (is_a($this, 'egw_cache_provider_multiple'))
-				{
-					$locations[$location] = $data;
-					$mget_after_delete = $this->mget(array($level,__CLASS__,array($location)));
-					if (isset($mget_after_delete[$location]))
-					{
-						if ($verbose) echo "$label: mget_after_delete['$location']=".array2string($mget_after_delete[$location])." != NULL\n";
-						++$failed;
-					}
-				}
-				elseif (!is_null($data))	// emulation can NOT distinquish between null and not set
-				{
-					$locations[$location] = $data;
-				}
-				$add_after_delete = $this->add(array($level,__CLASS__,$location), $data, 10);
-				if ($add_after_delete !== true)
-				{
-					if ($verbose) echo "$label: add_after_delete=".array2string($add_after_delete)."\n";
-					++$failed;
-				}
-				else
-				{
-					$get_after_add = $this->get(array($level,__CLASS__,$location));
-					if ($get_after_add !== $data)
-					{
-						if ($verbose) echo "$label: get_after_add=".array2string($get_after_add)." !== ".array2string($data)."\n";
-						++$failed;
-					}
-				}
-			}
-			// get all above in one request
-			$keys = array_keys($locations);
-			$keys_bogus = array_merge(array('not-set'),array_keys($locations),array('not-set-too'));
-			if (is_a($this, 'egw_cache_provider_multiple'))
-			{
-				$mget = $this->mget(array($level,__CLASS__,$keys));
-				$mget_bogus = $this->mget(array($level,__CLASS__,$keys_bogus));
-			/* egw_cache::getCache() gives a different result, as it does NOT use $level direkt
-			}
-			else
-			{
-				$mget = egw_cache::getCache($level, __CLASS__, $keys);
-				$mget_bogus = egw_cache::getCache($level, __CLASS__, $keys_bogus);
-			}*/
-				if ($mget !== $locations)
-				{
-					if ($verbose) echo "$label: mget=\n".array2string($mget)." !==\n".array2string($locations)."\n";
-					++$failed;
-				}
-				if ($mget_bogus !== $locations)
-				{
-					if ($verbose) echo "$label: mget(".array2string($keys_bogus).")=\n".array2string($mget_bogus)." !==\n".array2string($locations)."\n";
-					++$failed;
-				}
-			}
-		}
-
-		return $failed;
-	}
-
-	/**
-	 * Delete all data under given keys
-	 *
-	 * Providers can return false, if they do not support flushing part of the cache (eg. memcache)
-	 *
-	 * @param array $keys eg. array($level,$app,$location)
-	 * @return boolean true on success, false on error (eg. $key not set)
-	 */
-	function flush(array $keys)
-	{
-		unset($keys);	// required by function signature
-		return false;
-	}
-}
-
-// some testcode, if this file is called via it's URL
-// can be run on command-line: sudo php -d apc.enable_cli=1 -f phpgwapi/inc/class.egw_cache.inc.php
-/*if (isset($_SERVER['SCRIPT_FILENAME']) && realpath($_SERVER['SCRIPT_FILENAME']) == __FILE__)
-{
-	if (!isset($_SERVER['HTTP_HOST']))
-	{
-		chdir(dirname(__FILE__));	// to enable our relative pathes to work
-	}
-	$GLOBALS['egw_info'] = array(
-		'flags' => array(
-			'noapi' => true,
-		),
-	);
-	include_once '../../header.inc.php';
-
-	if (isset($_SERVER['HTTP_HOST'])) echo "
\n";
-
-	foreach(array(
-		'egw_cache_apc' => array(),
-		'egw_cache_memcache' => array('localhost'),
-		'egw_cache_files' => array('/tmp'),
-	) as $class => $param)
-	{
-		echo "Checking $class:\n";
-		try {
-			$start = microtime(true);
-			$provider = new $class($param);
-			$n = 100;
-			for($i=1; $i <= $n; ++$i)
-			{
-				$failed = $provider->check($i == 1);
-			}
-			printf("$failed checks failed, $n iterations took %5.3f sec\n\n", microtime(true)-$start);
-		}
-		catch (Exception $e) {
-			printf($e->getMessage()."\n\n");
-		}
-	}
-}*/
-
-// setting apc as default provider, if apc_fetch function exists AND further checks in egw_cache_apc recommed it
-if (is_null(egw_cache::$default_provider))
-{
-	egw_cache::$default_provider = function_exists('apc_fetch') && egw_cache_apc::available() ? 'egw_cache_apc' : 'egw_cache_files';
-}
+class egw_cache_apc extends Api\Cache\Apc {}
+class egw_cache_files extends Api\Cache\Files {}
+class egw_cache_memcache extends Api\Cache\Memcache {}
+class egw_cache_memcached extends Api\Cache\Memcached {}