/framework/Lock/lib/Horde/Lock/Sql.php

https://github.com/ewandor/horde · PHP · 253 lines · 139 code · 31 blank · 83 comment · 14 complexity · b41cda7f4440a757af1c217bfbfc18d2 MD5 · raw file

  1. <?php
  2. /**
  3. * The Horde_Lock_Sql driver implements a storage backend for the Horde_Lock
  4. * API.
  5. *
  6. * The table structure for the locks is as follows:
  7. * <pre>
  8. * CREATE TABLE horde_locks (
  9. * lock_id VARCHAR(36) NOT NULL,
  10. * lock_owner VARCHAR(32) NOT NULL,
  11. * lock_scope VARCHAR(32) NOT NULL,
  12. * lock_principal VARCHAR(255) NOT NULL,
  13. * lock_origin_timestamp BIGINT NOT NULL,
  14. * lock_update_timestamp BIGINT NOT NULL,
  15. * lock_expiry_timestamp BIGINT NOT NULL,
  16. * lock_type TINYINT NOT NULL,
  17. *
  18. * PRIMARY KEY (lock_id)
  19. * );
  20. * </pre>
  21. *
  22. * Copyright 2008-2012 Horde LLC (http://www.horde.org/)
  23. *
  24. * See the enclosed file COPYING for license information (LGPL). If you did
  25. * not receive this file, see http://www.horde.org/licenses/lgpl21.
  26. *
  27. * @author Ben Klang <bklang@horde.org>
  28. * @category Horde
  29. * @package Lock
  30. */
  31. class Horde_Lock_Sql extends Horde_Lock
  32. {
  33. /**
  34. * Handle for the current database connection.
  35. *
  36. * @var Horde_Db_Adapter
  37. */
  38. private $_db;
  39. /**
  40. * Constructor.
  41. *
  42. * @param array $params Parameters:
  43. * <pre>
  44. * 'db' - (Horde_Db_Adapter) [REQUIRED] The DB instance.
  45. * 'table' - (string) The name of the lock table in 'database'.
  46. * DEFAULT: 'horde_locks'
  47. * </pre>
  48. *
  49. * @throws Horde_Lock_Exception
  50. */
  51. public function __construct($params = array())
  52. {
  53. if (!isset($params['db'])) {
  54. throw new Horde_Lock_Exception('Missing db parameter.');
  55. }
  56. $this->_db = $params['db'];
  57. unset($params['db']);
  58. $params = array_merge(array(
  59. 'table' => 'horde_locks'
  60. ), $params);
  61. parent::__construct($params);
  62. /* Only do garbage collection if asked for, and then only 0.1% of the
  63. * time we create an object. */
  64. if (rand(0, 999) == 0) {
  65. register_shutdown_function(array($this, 'doGC'));
  66. }
  67. }
  68. /**
  69. * Return an array of information about the requested lock.
  70. *
  71. * @see Horde_Lock_Base::getLockInfo()
  72. */
  73. public function getLockInfo($lockid)
  74. {
  75. $now = time();
  76. $sql = 'SELECT lock_id, lock_owner, lock_scope, lock_principal, ' .
  77. 'lock_origin_timestamp, lock_update_timestamp, ' .
  78. 'lock_expiry_timestamp, lock_type FROM ' . $this->_params['table'] .
  79. ' WHERE lock_id = ? AND lock_expiry_timestamp >= ?';
  80. $values = array($lockid, $now);
  81. try {
  82. return $this->_db->selectOne($sql, $values);
  83. } catch (Horde_Db_Exception $e) {
  84. throw new Horde_Lock_Exception($e);
  85. }
  86. }
  87. /**
  88. * Return a list of valid locks with the option to limit the results
  89. * by principal, scope and/or type.
  90. *
  91. * @see Horde_Lock_Base::getLocks()
  92. */
  93. public function getLocks($scope = null, $principal = null, $type = null)
  94. {
  95. $now = time();
  96. $sql = 'SELECT lock_id, lock_owner, lock_scope, lock_principal, ' .
  97. 'lock_origin_timestamp, lock_update_timestamp, ' .
  98. 'lock_expiry_timestamp, lock_type FROM ' .
  99. $this->_params['table'] . ' WHERE lock_expiry_timestamp >= ?';
  100. $values = array($now);
  101. // Check to see if we need to filter the results
  102. if (!empty($principal)) {
  103. $sql .= ' AND lock_principal = ?';
  104. $values[] = $principal;
  105. }
  106. if (!empty($scope)) {
  107. $sql .= ' AND lock_scope = ?';
  108. $values[] = $scope;
  109. }
  110. if (!empty($type)) {
  111. $sql .= ' AND lock_type = ?';
  112. $values[] = $type;
  113. }
  114. try {
  115. $result = $this->_db->selectAll($sql, $values);
  116. } catch (Horde_Db_Exception $e) {
  117. throw new Horde_Lock_Exception($e);
  118. }
  119. $locks = array();
  120. foreach ($result as $row) {
  121. $locks[$row['lock_id']] = $row;
  122. }
  123. return $locks;
  124. }
  125. /**
  126. * Extend the valid lifetime of a valid lock to now + $newtimeout.
  127. *
  128. * @see Horde_Lock_Base::resetLock()
  129. */
  130. public function resetLock($lockid, $extend)
  131. {
  132. $now = time();
  133. if (!$this->getLockInfo($lockid)) {
  134. return false;
  135. }
  136. $expiry = $now + $extend;
  137. $sql = 'UPDATE ' . $this->_params['table'] . ' SET ' .
  138. 'lock_update_timestamp = ?, lock_expiry_timestamp = ? ' .
  139. 'WHERE lock_id = ?';
  140. $values = array($now, $expiry, $lockid);
  141. try {
  142. $this->_db->update($sql, $values);
  143. } catch (Horde_Db_Exception $e) {
  144. throw new Horde_Lock_Exception($e);
  145. }
  146. return true;
  147. }
  148. /**
  149. * Sets a lock on the requested principal and returns the generated lock
  150. * ID.
  151. *
  152. * @see Horde_Lock_Base::setLock()
  153. */
  154. public function setLock($requestor, $scope, $principal,
  155. $lifetime = 1, $type = Horde_Lock::TYPE_SHARED)
  156. {
  157. $oldlocks = $this->getLocks($scope, $principal, Horde_Lock::TYPE_EXCLUSIVE);
  158. if (count($oldlocks) != 0) {
  159. // An exclusive lock exists. Deny the new request.
  160. if ($this->_logger) {
  161. $this->_logger->log(sprintf('Lock requested for %s denied due to existing exclusive lock.', $principal), 'NOTICE');
  162. }
  163. return false;
  164. }
  165. $lockid = (string)new Horde_Support_Uuid();
  166. $now = time();
  167. $expiration = $now + $lifetime;
  168. $sql = 'INSERT INTO ' . $this->_params['table'] . ' (lock_id, lock_owner, lock_scope, lock_principal, lock_origin_timestamp, lock_update_timestamp, lock_expiry_timestamp, lock_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)';
  169. $values = array($lockid, $requestor, $scope, $principal, $now, $now,
  170. $expiration, $type);
  171. try {
  172. $this->_db->insert($sql, $values);
  173. } catch (Horde_Db_Exception $e) {
  174. throw new Horde_Lock_Exception($e);
  175. }
  176. if ($this->_logger) {
  177. $this->_logger->log(sprintf('Lock %s set successfully by %s in scope %s on "%s"', $lockid, $requestor, $scope, $principal), 'DEBUG');
  178. }
  179. return $lockid;
  180. }
  181. /**
  182. * Removes a lock given the lock ID.
  183. *
  184. * @see Horde_Lock_Base::clearLock()
  185. */
  186. public function clearLock($lockid)
  187. {
  188. if (empty($lockid)) {
  189. throw new Horde_Lock_Exception('Must supply a valid lock ID.');
  190. }
  191. // Since we're trying to clear the lock we don't care
  192. // whether it is still valid or not. Unconditionally
  193. // remove it.
  194. $sql = 'DELETE FROM ' . $this->_params['table'] . ' WHERE lock_id = ?';
  195. $values = array($lockid);
  196. try {
  197. $this->_db->delete($sql, $values);
  198. } catch (Horde_Db_Exception $e) {
  199. throw new Horde_Lock_Exception($e);
  200. }
  201. if ($this->_logger) {
  202. $this->_logger->log(sprintf('Lock %s cleared successfully.', $lockid), 'DEBUG');
  203. }
  204. return true;
  205. }
  206. /**
  207. * Do garbage collection needed for the driver.
  208. */
  209. public function doGC()
  210. {
  211. $now = time();
  212. $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE ' .
  213. 'lock_expiry_timestamp < ? AND lock_expiry_timestamp != 0';
  214. $values = array($now);
  215. try {
  216. $result = $this->_db->delete($query, $values);
  217. if ($this->_logger) {
  218. $this->_logger->log(sprintf('Lock garbage collection cleared %d locks.', $result), 'DEBUG');
  219. }
  220. } catch (Horde_Db_Exception $e) {}
  221. }
  222. }