Queue.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Service_WindowsAzure
  17. * @subpackage Storage
  18. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://todo name_todo
  20. * @version $Id: Blob.php 24241 2009-07-22 09:43:13Z unknown $
  21. */
  22. /**
  23. * @see Zend_Service_WindowsAzure_Credentials_SharedKey
  24. */
  25. require_once 'Zend/Service/WindowsAzure/Credentials/SharedKey.php';
  26. /**
  27. * @see Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract
  28. */
  29. require_once 'Zend/Service/WindowsAzure/RetryPolicy/RetryPolicyAbstract.php';
  30. /**
  31. * @see Zend_Http_Client
  32. */
  33. require_once 'Zend/Http/Client.php';
  34. /**
  35. * @see Zend_Http_Response
  36. */
  37. require_once 'Zend/Http/Response.php';
  38. /**
  39. * @see Zend_Service_WindowsAzure_Storage
  40. */
  41. require_once 'Zend/Service/WindowsAzure/Storage.php';
  42. /**
  43. * Zend_Service_WindowsAzure_Storage_QueueInstance
  44. */
  45. require_once 'Zend/Service/WindowsAzure/Storage/QueueInstance.php';
  46. /**
  47. * Zend_Service_WindowsAzure_Storage_QueueMessage
  48. */
  49. require_once 'Zend/Service/WindowsAzure/Storage/QueueMessage.php';
  50. /**
  51. * @see Zend_Service_WindowsAzure_Exception
  52. */
  53. require_once 'Zend/Service/WindowsAzure/Exception.php';
  54. /**
  55. * @category Zend
  56. * @package Zend_Service_WindowsAzure
  57. * @subpackage Storage
  58. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  59. * @license http://framework.zend.com/license/new-bsd New BSD License
  60. */
  61. class Zend_Service_WindowsAzure_Storage_Queue extends Zend_Service_WindowsAzure_Storage
  62. {
  63. /**
  64. * Maximal message size (in bytes)
  65. */
  66. const MAX_MESSAGE_SIZE = 8388608;
  67. /**
  68. * Maximal message ttl (in seconds)
  69. */
  70. const MAX_MESSAGE_TTL = 604800;
  71. /**
  72. * Creates a new Zend_Service_WindowsAzure_Storage_Queue instance
  73. *
  74. * @param string $host Storage host name
  75. * @param string $accountName Account name for Windows Azure
  76. * @param string $accountKey Account key for Windows Azure
  77. * @param boolean $usePathStyleUri Use path-style URI's
  78. * @param Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy Retry policy to use when making requests
  79. */
  80. public function __construct($host = Zend_Service_WindowsAzure_Storage::URL_DEV_QUEUE, $accountName = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_ACCOUNT, $accountKey = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::DEVSTORE_KEY, $usePathStyleUri = false, Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy = null)
  81. {
  82. parent::__construct($host, $accountName, $accountKey, $usePathStyleUri, $retryPolicy);
  83. // API version
  84. $this->_apiVersion = '2009-04-14';
  85. }
  86. /**
  87. * Check if a queue exists
  88. *
  89. * @param string $queueName Queue name
  90. * @return boolean
  91. */
  92. public function queueExists($queueName = '')
  93. {
  94. if ($queueName === '') {
  95. throw new Zend_Service_WindowsAzure_Exception('Queue name is not specified.');
  96. }
  97. if (!self::isValidQueueName($queueName)) {
  98. throw new Zend_Service_WindowsAzure_Exception('Queue name does not adhere to queue naming conventions. See http://msdn.microsoft.com/en-us/library/dd179349.aspx for more information.');
  99. }
  100. // List queues
  101. $queues = $this->listQueues($queueName, 1);
  102. foreach ($queues as $queue) {
  103. if ($queue->Name == $queueName) {
  104. return true;
  105. }
  106. }
  107. return false;
  108. }
  109. /**
  110. * Create queue
  111. *
  112. * @param string $queueName Queue name
  113. * @param array $metadata Key/value pairs of meta data
  114. * @return object Queue properties
  115. * @throws Zend_Service_WindowsAzure_Exception
  116. */
  117. public function createQueue($queueName = '', $metadata = array())
  118. {
  119. if ($queueName === '') {
  120. throw new Zend_Service_WindowsAzure_Exception('Queue name is not specified.');
  121. }
  122. if (!self::isValidQueueName($queueName)) {
  123. throw new Zend_Service_WindowsAzure_Exception('Queue name does not adhere to queue naming conventions. See http://msdn.microsoft.com/en-us/library/dd179349.aspx for more information.');
  124. }
  125. // Create metadata headers
  126. $headers = array();
  127. foreach ($metadata as $key => $value) {
  128. $headers["x-ms-meta-" . strtolower($key)] = $value;
  129. }
  130. // Perform request
  131. $response = $this->_performRequest($queueName, '', Zend_Http_Client::PUT, $headers);
  132. if ($response->isSuccessful()) {
  133. return new Zend_Service_WindowsAzure_Storage_QueueInstance(
  134. $queueName,
  135. $metadata
  136. );
  137. } else {
  138. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  139. }
  140. }
  141. /**
  142. * Get queue
  143. *
  144. * @param string $queueName Queue name
  145. * @return Zend_Service_WindowsAzure_Storage_QueueInstance
  146. * @throws Zend_Service_WindowsAzure_Exception
  147. */
  148. public function getQueue($queueName = '')
  149. {
  150. if ($queueName === '') {
  151. throw new Zend_Service_WindowsAzure_Exception('Queue name is not specified.');
  152. }
  153. if (!self::isValidQueueName($queueName)) {
  154. throw new Zend_Service_WindowsAzure_Exception('Queue name does not adhere to queue naming conventions. See http://msdn.microsoft.com/en-us/library/dd179349.aspx for more information.');
  155. }
  156. // Perform request
  157. $response = $this->_performRequest($queueName, '?comp=metadata', Zend_Http_Client::GET);
  158. if ($response->isSuccessful()) {
  159. // Parse metadata
  160. $metadata = array();
  161. foreach ($response->getHeaders() as $key => $value) {
  162. if (substr(strtolower($key), 0, 10) == "x-ms-meta-") {
  163. $metadata[str_replace("x-ms-meta-", '', strtolower($key))] = $value;
  164. }
  165. }
  166. // Return queue
  167. $queue = new Zend_Service_WindowsAzure_Storage_QueueInstance(
  168. $queueName,
  169. $metadata
  170. );
  171. $queue->ApproximateMessageCount = intval($response->getHeader('x-ms-approximate-message-count'));
  172. return $queue;
  173. } else {
  174. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  175. }
  176. }
  177. /**
  178. * Get queue metadata
  179. *
  180. * @param string $queueName Queue name
  181. * @return array Key/value pairs of meta data
  182. * @throws Zend_Service_WindowsAzure_Exception
  183. */
  184. public function getQueueMetadata($queueName = '')
  185. {
  186. if ($queueName === '') {
  187. throw new Zend_Service_WindowsAzure_Exception('Queue name is not specified.');
  188. }
  189. if (!self::isValidQueueName($queueName)) {
  190. throw new Zend_Service_WindowsAzure_Exception('Queue name does not adhere to queue naming conventions. See http://msdn.microsoft.com/en-us/library/dd179349.aspx for more information.');
  191. }
  192. return $this->getQueue($queueName)->Metadata;
  193. }
  194. /**
  195. * Set queue metadata
  196. *
  197. * Calling the Set Queue Metadata operation overwrites all existing metadata that is associated with the queue. It's not possible to modify an individual name/value pair.
  198. *
  199. * @param string $queueName Queue name
  200. * @param array $metadata Key/value pairs of meta data
  201. * @throws Zend_Service_WindowsAzure_Exception
  202. */
  203. public function setQueueMetadata($queueName = '', $metadata = array())
  204. {
  205. if ($queueName === '') {
  206. throw new Zend_Service_WindowsAzure_Exception('Queue name is not specified.');
  207. }
  208. if (!self::isValidQueueName($queueName)) {
  209. throw new Zend_Service_WindowsAzure_Exception('Queue name does not adhere to queue naming conventions. See http://msdn.microsoft.com/en-us/library/dd179349.aspx for more information.');
  210. }
  211. if (count($metadata) == 0) {
  212. return;
  213. }
  214. // Create metadata headers
  215. $headers = array();
  216. foreach ($metadata as $key => $value) {
  217. $headers["x-ms-meta-" . strtolower($key)] = $value;
  218. }
  219. // Perform request
  220. $response = $this->_performRequest($queueName, '?comp=metadata', Zend_Http_Client::PUT, $headers);
  221. if (!$response->isSuccessful()) {
  222. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  223. }
  224. }
  225. /**
  226. * Delete queue
  227. *
  228. * @param string $queueName Queue name
  229. * @throws Zend_Service_WindowsAzure_Exception
  230. */
  231. public function deleteQueue($queueName = '')
  232. {
  233. if ($queueName === '') {
  234. throw new Zend_Service_WindowsAzure_Exception('Queue name is not specified.');
  235. }
  236. if (!self::isValidQueueName($queueName)) {
  237. throw new Zend_Service_WindowsAzure_Exception('Queue name does not adhere to queue naming conventions. See http://msdn.microsoft.com/en-us/library/dd179349.aspx for more information.');
  238. }
  239. // Perform request
  240. $response = $this->_performRequest($queueName, '', Zend_Http_Client::DELETE);
  241. if (!$response->isSuccessful()) {
  242. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  243. }
  244. }
  245. /**
  246. * List queues
  247. *
  248. * @param string $prefix Optional. Filters the results to return only queues whose name begins with the specified prefix.
  249. * @param int $maxResults Optional. Specifies the maximum number of queues to return per call to Azure storage. This does NOT affect list size returned by this function. (maximum: 5000)
  250. * @param string $marker Optional string value that identifies the portion of the list to be returned with the next list operation.
  251. * @param int $currentResultCount Current result count (internal use)
  252. * @return array
  253. * @throws Zend_Service_WindowsAzure_Exception
  254. */
  255. public function listQueues($prefix = null, $maxResults = null, $marker = null, $currentResultCount = 0)
  256. {
  257. // Build query string
  258. $queryString = '?comp=list';
  259. if (!is_null($prefix)) {
  260. $queryString .= '&prefix=' . $prefix;
  261. }
  262. if (!is_null($maxResults)) {
  263. $queryString .= '&maxresults=' . $maxResults;
  264. }
  265. if (!is_null($marker)) {
  266. $queryString .= '&marker=' . $marker;
  267. }
  268. // Perform request
  269. $response = $this->_performRequest('', $queryString, Zend_Http_Client::GET);
  270. if ($response->isSuccessful()) {
  271. $xmlQueues = $this->_parseResponse($response)->Queues->Queue;
  272. $xmlMarker = (string)$this->_parseResponse($response)->NextMarker;
  273. $queues = array();
  274. if (!is_null($xmlQueues)) {
  275. for ($i = 0; $i < count($xmlQueues); $i++) {
  276. $queues[] = new Zend_Service_WindowsAzure_Storage_QueueInstance(
  277. (string)$xmlQueues[$i]->QueueName
  278. );
  279. }
  280. }
  281. $currentResultCount = $currentResultCount + count($queues);
  282. if (!is_null($maxResults) && $currentResultCount < $maxResults) {
  283. if (!is_null($xmlMarker) && $xmlMarker != '') {
  284. $queues = array_merge($queues, $this->listQueues($prefix, $maxResults, $xmlMarker, $currentResultCount));
  285. }
  286. }
  287. if (!is_null($maxResults) && count($queues) > $maxResults) {
  288. $queues = array_slice($queues, 0, $maxResults);
  289. }
  290. return $queues;
  291. } else {
  292. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  293. }
  294. }
  295. /**
  296. * Put message into queue
  297. *
  298. * @param string $queueName Queue name
  299. * @param string $message Message
  300. * @param int $ttl Message Time-To-Live (in seconds). Defaults to 7 days if the parameter is omitted.
  301. * @throws Zend_Service_WindowsAzure_Exception
  302. */
  303. public function putMessage($queueName = '', $message = '', $ttl = null)
  304. {
  305. if ($queueName === '') {
  306. throw new Zend_Service_WindowsAzure_Exception('Queue name is not specified.');
  307. }
  308. if (!self::isValidQueueName($queueName)) {
  309. throw new Zend_Service_WindowsAzure_Exception('Queue name does not adhere to queue naming conventions. See http://msdn.microsoft.com/en-us/library/dd179349.aspx for more information.');
  310. }
  311. if (strlen($message) > self::MAX_MESSAGE_SIZE) {
  312. throw new Zend_Service_WindowsAzure_Exception('Message is too big. Message content should be < 8KB.');
  313. }
  314. if ($message == '') {
  315. throw new Zend_Service_WindowsAzure_Exception('Message is not specified.');
  316. }
  317. if (!is_null($ttl) && ($ttl <= 0 || $ttl > self::MAX_MESSAGE_SIZE)) {
  318. throw new Zend_Service_WindowsAzure_Exception('Message TTL is invalid. Maximal TTL is 7 days (' . self::MAX_MESSAGE_SIZE . ' seconds) and should be greater than zero.');
  319. }
  320. // Build query string
  321. $queryString = '';
  322. if (!is_null($ttl)) {
  323. $queryString .= '?messagettl=' . $ttl;
  324. }
  325. // Build body
  326. $rawData = '';
  327. $rawData .= '<QueueMessage>';
  328. $rawData .= ' <MessageText>' . base64_encode($message) . '</MessageText>';
  329. $rawData .= '</QueueMessage>';
  330. // Perform request
  331. $response = $this->_performRequest($queueName . '/messages', $queryString, Zend_Http_Client::POST, array(), false, $rawData);
  332. if (!$response->isSuccessful()) {
  333. throw new Zend_Service_WindowsAzure_Exception('Error putting message into queue.');
  334. }
  335. }
  336. /**
  337. * Get queue messages
  338. *
  339. * @param string $queueName Queue name
  340. * @param string $numOfMessages Optional. A nonzero integer value that specifies the number of messages to retrieve from the queue, up to a maximum of 32. By default, a single message is retrieved from the queue with this operation.
  341. * @param int $visibilityTimeout Optional. An integer value that specifies the message's visibility timeout in seconds. The maximum value is 2 hours. The default message visibility timeout is 30 seconds.
  342. * @param string $peek Peek only?
  343. * @return array
  344. * @throws Zend_Service_WindowsAzure_Exception
  345. */
  346. public function getMessages($queueName = '', $numOfMessages = 1, $visibilityTimeout = null, $peek = false)
  347. {
  348. if ($queueName === '') {
  349. throw new Zend_Service_WindowsAzure_Exception('Queue name is not specified.');
  350. }
  351. if (!self::isValidQueueName($queueName)) {
  352. throw new Zend_Service_WindowsAzure_Exception('Queue name does not adhere to queue naming conventions. See http://msdn.microsoft.com/en-us/library/dd179349.aspx for more information.');
  353. }
  354. if ($numOfMessages < 1 || $numOfMessages > 32 || intval($numOfMessages) != $numOfMessages) {
  355. throw new Zend_Service_WindowsAzure_Exception('Invalid number of messages to retrieve.');
  356. }
  357. if (!is_null($visibilityTimeout) && ($visibilityTimeout <= 0 || $visibilityTimeout > 7200)) {
  358. throw new Zend_Service_WindowsAzure_Exception('Visibility timeout is invalid. Maximum value is 2 hours (7200 seconds) and should be greater than zero.');
  359. }
  360. // Build query string
  361. $query = array();
  362. if ($peek) {
  363. $query[] = 'peekonly=true';
  364. }
  365. if ($numOfMessages > 1) {
  366. $query[] = 'numofmessages=' . $numOfMessages;
  367. }
  368. if (!$peek && !is_null($visibilityTimeout)) {
  369. $query[] = 'visibilitytimeout=' . $visibilityTimeout;
  370. }
  371. $queryString = '?' . implode('&', $query);
  372. // Perform request
  373. $response = $this->_performRequest($queueName . '/messages', $queryString, Zend_Http_Client::GET);
  374. if ($response->isSuccessful()) {
  375. // Parse results
  376. $result = $this->_parseResponse($response);
  377. if (!$result) {
  378. return array();
  379. }
  380. $xmlMessages = null;
  381. if (count($result->QueueMessage) > 1) {
  382. $xmlMessages = $result->QueueMessage;
  383. } else {
  384. $xmlMessages = array($result->QueueMessage);
  385. }
  386. $messages = array();
  387. for ($i = 0; $i < count($xmlMessages); $i++) {
  388. $messages[] = new Zend_Service_WindowsAzure_Storage_QueueMessage(
  389. (string)$xmlMessages[$i]->MessageId,
  390. (string)$xmlMessages[$i]->InsertionTime,
  391. (string)$xmlMessages[$i]->ExpirationTime,
  392. ($peek ? '' : (string)$xmlMessages[$i]->PopReceipt),
  393. ($peek ? '' : (string)$xmlMessages[$i]->TimeNextVisible),
  394. base64_decode((string)$xmlMessages[$i]->MessageText)
  395. );
  396. }
  397. return $messages;
  398. } else {
  399. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  400. }
  401. }
  402. /**
  403. * Peek queue messages
  404. *
  405. * @param string $queueName Queue name
  406. * @param string $numOfMessages Optional. A nonzero integer value that specifies the number of messages to retrieve from the queue, up to a maximum of 32. By default, a single message is retrieved from the queue with this operation.
  407. * @return array
  408. * @throws Zend_Service_WindowsAzure_Exception
  409. */
  410. public function peekMessages($queueName = '', $numOfMessages = 1)
  411. {
  412. return $this->getMessages($queueName, $numOfMessages, null, true);
  413. }
  414. /**
  415. * Clear queue messages
  416. *
  417. * @param string $queueName Queue name
  418. * @throws Zend_Service_WindowsAzure_Exception
  419. */
  420. public function clearMessages($queueName = '')
  421. {
  422. if ($queueName === '') {
  423. throw new Zend_Service_WindowsAzure_Exception('Queue name is not specified.');
  424. }
  425. if (!self::isValidQueueName($queueName)) {
  426. throw new Zend_Service_WindowsAzure_Exception('Queue name does not adhere to queue naming conventions. See http://msdn.microsoft.com/en-us/library/dd179349.aspx for more information.');
  427. }
  428. // Perform request
  429. $response = $this->_performRequest($queueName . '/messages', '', Zend_Http_Client::DELETE);
  430. if (!$response->isSuccessful()) {
  431. throw new Zend_Service_WindowsAzure_Exception('Error clearing messages from queue.');
  432. }
  433. }
  434. /**
  435. * Delete queue message
  436. *
  437. * @param string $queueName Queue name
  438. * @param Zend_Service_WindowsAzure_Storage_QueueMessage $message Message to delete from queue. A message retrieved using "peekMessages" can NOT be deleted!
  439. * @throws Zend_Service_WindowsAzure_Exception
  440. */
  441. public function deleteMessage($queueName = '', Zend_Service_WindowsAzure_Storage_QueueMessage $message)
  442. {
  443. if ($queueName === '') {
  444. throw new Zend_Service_WindowsAzure_Exception('Queue name is not specified.');
  445. }
  446. if (!self::isValidQueueName($queueName)) {
  447. throw new Zend_Service_WindowsAzure_Exception('Queue name does not adhere to queue naming conventions. See http://msdn.microsoft.com/en-us/library/dd179349.aspx for more information.');
  448. }
  449. if ($message->PopReceipt == '') {
  450. throw new Zend_Service_WindowsAzure_Exception('A message retrieved using "peekMessages" can NOT be deleted! Use "getMessages" instead.');
  451. }
  452. // Perform request
  453. $response = $this->_performRequest($queueName . '/messages/' . $message->MessageId, '?popreceipt=' . $message->PopReceipt, Zend_Http_Client::DELETE);
  454. if (!$response->isSuccessful()) {
  455. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  456. }
  457. }
  458. /**
  459. * Is valid queue name?
  460. *
  461. * @param string $queueName Queue name
  462. * @return boolean
  463. */
  464. public static function isValidQueueName($queueName = '')
  465. {
  466. if (!ereg("^[a-z0-9][a-z0-9-]*$", $queueName)) {
  467. return false;
  468. }
  469. if (strpos($queueName, '--') !== false) {
  470. return false;
  471. }
  472. if (strtolower($queueName) != $queueName) {
  473. return false;
  474. }
  475. if (strlen($queueName) < 3 || strlen($queueName) > 63) {
  476. return false;
  477. }
  478. if (substr($queueName, -1) == '-') {
  479. return false;
  480. }
  481. return true;
  482. }
  483. /**
  484. * Get error message from Zend_Http_Response
  485. *
  486. * @param Zend_Http_Response $response Repsonse
  487. * @param string $alternativeError Alternative error message
  488. * @return string
  489. */
  490. protected function _getErrorMessage(Zend_Http_Response $response, $alternativeError = 'Unknown error.')
  491. {
  492. $response = $this->_parseResponse($response);
  493. if ($response && $response->Message) {
  494. return (string)$response->Message;
  495. } else {
  496. return $alternativeError;
  497. }
  498. }
  499. }