Files
server/usr/share/psa-pear/pear/php/Horde/Lock/Mongo.php
2026-01-07 20:52:11 +01:00

312 lines
8.1 KiB
PHP

<?php
/**
* Copyright 2013-2016 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you did
* not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @category Horde
* @copyright 2013-2016 Horde LLC
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Lock
*/
/**
* Lock storage in a MongoDB database.
*
* @author Michael Slusarz <slusarz@horde.org>
* @category Horde
* @copyright 2013-2016 Horde LLC
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Lock
*/
class Horde_Lock_Mongo extends Horde_Lock
{
/* Field names. */
const EXPIRY_TS = 'expiry_ts';
const LID = 'lid';
const ORIGIN_TS = 'origin_ts';
const OWNER = 'owner';
const PRINCIPAL = 'principal';
const SCOPE = 'scope';
const TYPE = 'type';
const UPDATE_TS = 'update_ts';
/**
* The MongoDB Collection object for the cache data.
*
* @var MongoCollection
*/
protected $_db;
/**
* Ugly hack: lock driver written with assumption that it returns data
* as defined by SQL columns. So need to do mapping in this driver.
*
* @var array
*/
protected $_map = array(
self::EXPIRY_TS => 'lock_expiry_timestamp',
self::LID => 'lock_id',
self::ORIGIN_TS => 'lock_origin_timestamp',
self::OWNER => 'lock_owner',
self::PRINCIPAL => 'lock_principal',
self::SCOPE => 'lock_scope',
self::TYPE => 'lock_type',
self::UPDATE_TS => 'lock_update_timestamp'
);
/**
* Constructor.
*
* @param array $params Parameters:
* <pre>
* - collection: (string) The collection name.
* - mongo_db: [REQUIRED] (Horde_Mongo_Client) A MongoDB client object.
* </pre>
*/
public function __construct(array $params = array())
{
if (!isset($params['mongo_db'])) {
throw new InvalidArgumentException('Missing mongo_db parameter.');
}
parent::__construct(array_merge(array(
'collection' => 'horde_locks'
), $params));
$this->_db = $this->_params['mongo_db']
->selectCollection(null, $this->_params['collection']);
}
/**
* Destructor.
*/
public function __destruct()
{
if (!rand(0, 999)) {
$this->gc();
}
}
/**
*/
public function getLockInfo($lockid)
{
$query = array(
self::LID => $lockid,
'$or' => array(
array(self::EXPIRY_TS => array('$gte' => time()),),
array(self::EXPIRY_TS => Horde_Lock::PERMANENT)
)
);
try {
return $this->_mapFields($this->_db->findOne($query));
} catch (MongoException $e) {
throw new Horde_Lock_Exception($e);
}
}
/**
*/
public function getLocks($scope = null, $principal = null, $type = null)
{
$query = array(
'$or' => array(
array(self::EXPIRY_TS => array('$gte' => time()),),
array(self::EXPIRY_TS => Horde_Lock::PERMANENT)
)
);
// Check to see if we need to filter the results
if (!empty($principal)) {
$query[self::PRINCIPAL] = $principal;
}
if (!empty($scope)) {
$query[self::SCOPE] = $scope;
}
if (!empty($type)) {
$query[self::TYPE] = $type;
}
try {
$result = $this->_db->find($query);
} catch (MongoException $e) {
throw new Horde_Lock_Exception($e);
}
$locks = array();
foreach ($result as $val) {
$locks[$val[self::LID]] = $this->_mapFields($val);
}
return $locks;
}
/**
*/
public function resetLock($lockid, $lifetime)
{
if (!$this->getLockInfo($lockid)) {
return false;
}
$now = time();
$expiration = ($lifetime == Horde_Lock::PERMANENT)
? Horde_Lock::PERMANENT
: ($now + $lifetime);
try {
$this->_db->update(
array(
self::EXPIRY_TS => array('$ne' => Horde_Lock::PERMANENT),
self::LID => $lockid
),
array(
'$set' => array(
self::EXPIRY_TS => $expiration,
self::UPDATE_TS => $now
)
)
);
} catch (MongoException $e) {
throw new Horde_Lock_Exception($e);
}
return true;
}
/**
*/
public function setLock($requestor, $scope, $principal,
$lifetime = 1, $type = Horde_Lock::TYPE_SHARED)
{
$oldlocks = $this->getLocks(
$scope,
$principal,
($type == Horde_Lock::TYPE_SHARED) ? Horde_Lock::TYPE_EXCLUSIVE : null
);
if (count($oldlocks) != 0) {
// A lock exists. Deny the new request.
if ($this->_logger) {
$this->_logger->log(
sprintf(
'Lock requested for %s denied due to existing lock.',
$principal
),
'NOTICE'
);
}
return false;
}
$lockid = strval(new Horde_Support_Uuid());
$now = time();
$expiration = ($lifetime == Horde_Lock::PERMANENT)
? Horde_Lock::PERMANENT
: ($now + $lifetime);
try {
$doc = array(
self::EXPIRY_TS => $expiration,
self::LID => $lockid,
self::ORIGIN_TS => $now,
self::OWNER => $requestor,
self::PRINCIPAL => $principal,
self::SCOPE => $scope,
self::TYPE => $type,
self::UPDATE_TS => $now
);
$this->_db->insert($doc);
} catch (MongoException $e) {
throw new Horde_Lock_Exception($e);
}
if ($this->_logger) {
$this->_logger->log(
sprintf(
'Lock %s set successfully by %s in scope %s on "%s"',
$lockid, $requestor, $scope, $principal
),
'DEBUG'
);
}
return $lockid;
}
/**
*/
public function clearLock($lockid)
{
if (empty($lockid)) {
throw new Horde_Lock_Exception('Must supply a valid lock ID.');
}
try {
/* Since we're trying to clear the lock we don't care whether it is
* still valid or not. Unconditionally remove it. */
$this->_db->remove(array(self::LID => $lockid));
} catch (MongoException $e) {
throw new Horde_Lock_Exception($e);
}
if ($this->_logger) {
$this->_logger->log(
sprintf('Lock %s cleared successfully.', $lockid), 'DEBUG'
);
}
return true;
}
/**
* Do garbage collection needed for the driver.
*/
public function gc()
{
try {
$result = $this->_db->remove(array(
self::EXPIRY_TS => array(
'$lt' => time(),
'$ne' => Horde_Lock::PERMANENT
)
));
if ($this->_logger) {
$this->_logger->log(
sprintf(
'Lock garbage collection cleared %d locks.',
$result['n']
),
'DEBUG'
);
}
} catch (MongoException $e) {}
}
/**
* Map return to SQL fields.
*
* @return array
*/
protected function _mapFields($res)
{
$out = array();
if ($res) {
foreach ($res as $key => $val) {
if (isset($this->_map[$key])) {
$out[$this->_map[$key]] = $val;
}
}
}
return $out;
}
}