Skip to content

Commit

Permalink
Add cache for ILS data
Browse files Browse the repository at this point in the history
Caching ILS data makes it possible to avoid e.g. repeated patronLogin calls.
  • Loading branch information
EreMaijala committed Oct 4, 2024
1 parent 26d051f commit 19422d1
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 6 deletions.
10 changes: 10 additions & 0 deletions config/vufind/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,16 @@ driver = Sample
; library_cards setting (see below).
allowUserLogin = true

; ILS data cache life time in seconds per function. The cache is used to avoid
; repeating requests too often (but often enough to not use stale data).
; Currently supported for the functions listed below. Default is 60 seconds.
; Set to 0 to disable caching.
;cacheLifeTime[patronLogin] = 60
;cacheLifeTime[getProxiedUsers] = 60
;cacheLifeTime[getProxyingUsers] = 60
;cacheLifeTime[getPickUpLocations] = 60
;cacheLifeTime[getPurchaseHistory] = 60

; loadNoILSOnFailure - Whether or not to load the NoILS driver if the main driver fails
loadNoILSOnFailure = false

Expand Down
147 changes: 143 additions & 4 deletions module/VuFind/src/VuFind/ILS/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
namespace VuFind\ILS;

use Laminas\Log\LoggerAwareInterface;
use Laminas\Session\Container;
use VuFind\Exception\BadConfig;
use VuFind\Exception\ILS as ILSException;
use VuFind\I18n\Translator\TranslatorAwareInterface;
Expand Down Expand Up @@ -64,6 +65,10 @@
*/
class Connection implements TranslatorAwareInterface, LoggerAwareInterface
{
use \VuFind\Cache\CacheTrait {
getCachedData as getSharedCachedData;
putCachedData as putSharedCachedData;
}
use \VuFind\I18n\Translator\TranslatorAwareTrait;
use \VuFind\Log\LoggerAwareTrait;

Expand Down Expand Up @@ -130,6 +135,39 @@ class Connection implements TranslatorAwareInterface, LoggerAwareInterface
*/
protected $request;

/**
* Cache life time per method
*
* @var array
*/
protected $cacheLifeTime = [
'patronLogin' => 60,
'getProxiedUsers' => 60,
'getProxyingUsers' => 60,
'getPickUpLocations' => 60,
'getPurchaseHistory' => 60,
];

/**
* Cache storage per method
*
* @var array
*/
protected $cacheStorage = [
'patronLogin' => 'session',
'getProxiedUsers' => 'session',
'getProxyingUsers' => 'session',
'getPickUpLocations' => 'session',
'getPurchaseHistory' => 'shared',
];

/**
* Session cache
*
* @var Container
*/
protected $sessionCache = null;

/**
* Constructor
*
Expand Down Expand Up @@ -171,6 +209,31 @@ public function setHoldConfig($settings)
return $this;
}

/**
* Set session container for cache.
*
* @param Container $container Session container
*
* @return Connection
*/
public function setSessionCache(Container $container)
{
$this->sessionCache = $container;
return $this;
}

/**
* Set cache lifetime settings
*
* @param array $settings Lifetime settings
*
* @return void
*/
public function setCacheLifeTime(array $settings): void
{
$this->cacheLifeTime = array_merge($this->cacheLifeTime, $settings);
}

/**
* Get class name of the driver object.
*
Expand Down Expand Up @@ -1198,17 +1261,15 @@ public function getStatusParser()
}

/**
* Default method -- pass along calls to the driver if available; return
* false otherwise. This allows custom functions to be implemented in
* the driver without constant modification to the connection class.
* Call an ILS method with failover to NoILS if configured.
*
* @param string $methodName The name of the called method.
* @param array $params Array of passed parameters.
*
* @throws ILSException
* @return mixed Varies by method (false if undefined method)
*/
public function __call($methodName, $params)
public function callIlsWithFailover($methodName, $params)
{
try {
if ($this->checkCapability($methodName, $params)) {
Expand All @@ -1227,4 +1288,82 @@ public function __call($methodName, $params)
'Cannot call method: ' . $this->getDriverClass() . '::' . $methodName
);
}

/**
* Get data for an ILS method from shared or session cache
*
* @param string $methodName The name of the called method.
* @param array $params Array of passed parameters.
*
* @return ?array
*/
protected function getCachedData($methodName, $params)
{
$cacheLifeTime = $this->cacheLifeTime[$methodName] ?? null;
$cacheStorage = $this->cacheStorage[$methodName] ?? null;
if (!$cacheLifeTime || !$cacheStorage) {
return null;
}
$cacheKey = $methodName . md5(serialize($params));
if ('shared' === $cacheStorage) {
return $this->getSharedCachedData($cacheKey);
}
if ($this->sessionCache && ($entry = $this->sessionCache[$cacheKey] ?? null)) {
if (time() - $entry['ts'] <= $cacheLifeTime) {
return $entry['data'];
}
unset($this->sessionCache[$cacheKey]);
}
return null;
}

/**
* Put data for an ILS method to shared or session cache.
*
* @param string $methodName The name of the called method.
* @param array $params Array of passed parameters.
* @param mixed $data Data to cache
*
* @return void
*/
protected function putCachedData($methodName, $params, $data): void
{
$cacheLifeTime = $this->cacheLifeTime[$methodName] ?? null;
$cacheStorage = $this->cacheStorage[$methodName] ?? null;
if (!$cacheLifeTime || !$cacheStorage) {
return;
}
$cacheKey = $methodName . md5(serialize($params));
if ('shared' === $cacheStorage) {
$this->putSharedCachedData($cacheKey, $data);
return;
}
if ($this->sessionCache) {
$this->sessionCache[$cacheKey] = [
'ts' => time(),
'data' => $data,
];
}
}

/**
* Default method -- pass along calls to the driver if available; return
* false otherwise. This allows custom functions to be implemented in
* the driver without constant modification to the connection class.
*
* @param string $methodName The name of the called method.
* @param array $params Array of passed parameters.
*
* @throws ILSException
* @return mixed Varies by method (false if undefined method)
*/
public function __call($methodName, $params)
{
if ($entry = $this->getCachedData($methodName, $params)) {
return $entry['data'];
}
$data = $this->callIlsWithFailover($methodName, $params);
$this->putCachedData($methodName, $params, compact('data'));
return $data;
}
}
12 changes: 10 additions & 2 deletions module/VuFind/src/VuFind/ILS/ConnectionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,23 @@ public function __invoke(
throw new \Exception('Unexpected options sent to factory.');
}
$configManager = $container->get(\VuFind\Config\PluginManager::class);
$config = $configManager->get('config');
$request = $container->get('Request');
$catalog = new $requestedName(
$configManager->get('config')->Catalog,
$config->Catalog,
$container->get(\VuFind\ILS\Driver\PluginManager::class),
$container->get(\VuFind\Config\PluginManager::class),
$request instanceof \Laminas\Http\Request ? $request : null
);
return $catalog->setHoldConfig(
$catalog->setHoldConfig(
$container->get(\VuFind\ILS\HoldSettings::class)
);
$catalog->setCacheStorage($container->get(\VuFind\Cache\Manager::class)->getCache('object'));
$manager = $container->get(\Laminas\Session\SessionManager::class);
$catalog->setSessionCache(new \Laminas\Session\Container('ILS', $manager));
if ($cacheLifeTime = $config->Catalog?->cacheLifeTime?->toArray()) {
$catalog->setCacheLifeTime($cacheLifeTime);
}
return $catalog;
}
}

0 comments on commit 19422d1

Please sign in to comment.