PageRenderTime 48ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/src/lib/Zend/Service/WindowsAzure/Storage/Table.php

https://bitbucket.org/mkrasuski/magento-ce
PHP | 931 lines | 519 code | 104 blank | 308 comment | 108 complexity | 3264067c6ef982f7bb216234451f5781 MD5 | raw file
  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-2015 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /**
  23. * @see Zend_Service_WindowsAzure_Storage_BatchStorageAbstract
  24. */
  25. #require_once 'Zend/Service/WindowsAzure/Storage/BatchStorageAbstract.php';
  26. /**
  27. * @see Zend_Service_WindowsAzure_Storage_TableInstance
  28. */
  29. #require_once 'Zend/Service/WindowsAzure/Storage/TableInstance.php';
  30. /**
  31. * @see Zend_Service_WindowsAzure_Storage_TableEntityQuery
  32. */
  33. #require_once 'Zend/Service/WindowsAzure/Storage/TableEntityQuery.php';
  34. /**
  35. * @see Zend_Service_WindowsAzure_Storage_DynamicTableEntity
  36. */
  37. #require_once 'Zend/Service/WindowsAzure/Storage/DynamicTableEntity.php';
  38. /**
  39. * @see Zend_Service_WindowsAzure_Credentials_SharedKeyLite
  40. */
  41. #require_once 'Zend/Service/WindowsAzure/Credentials/SharedKeyLite.php';
  42. /**
  43. * @category Zend
  44. * @package Zend_Service_WindowsAzure
  45. * @subpackage Storage
  46. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  47. * @license http://framework.zend.com/license/new-bsd New BSD License
  48. */
  49. class Zend_Service_WindowsAzure_Storage_Table
  50. extends Zend_Service_WindowsAzure_Storage_BatchStorageAbstract
  51. {
  52. /**
  53. * Throw Zend_Service_WindowsAzure_Exception when a property is not specified in Windows Azure?
  54. * Defaults to true, making behaviour similar to Windows Azure StorageClient in .NET.
  55. *
  56. * @var boolean
  57. */
  58. protected $_throwExceptionOnMissingData = true;
  59. /**
  60. * Throw Zend_Service_WindowsAzure_Exception when a property is not specified in Windows Azure?
  61. * Defaults to true, making behaviour similar to Windows Azure StorageClient in .NET.
  62. *
  63. * @param boolean $value
  64. */
  65. public function setThrowExceptionOnMissingData($value = true)
  66. {
  67. $this->_throwExceptionOnMissingData = $value;
  68. }
  69. /**
  70. * Throw Zend_Service_WindowsAzure_Exception when a property is not specified in Windows Azure?
  71. */
  72. public function getThrowExceptionOnMissingData()
  73. {
  74. return $this->_throwExceptionOnMissingData;
  75. }
  76. /**
  77. * Creates a new Zend_Service_WindowsAzure_Storage_Table instance
  78. *
  79. * @param string $host Storage host name
  80. * @param string $accountName Account name for Windows Azure
  81. * @param string $accountKey Account key for Windows Azure
  82. * @param boolean $usePathStyleUri Use path-style URI's
  83. * @param Zend_Service_WindowsAzure_RetryPolicy_RetryPolicyAbstract $retryPolicy Retry policy to use when making requests
  84. */
  85. public function __construct($host = Zend_Service_WindowsAzure_Storage::URL_DEV_TABLE, $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)
  86. {
  87. parent::__construct($host, $accountName, $accountKey, $usePathStyleUri, $retryPolicy);
  88. // Always use SharedKeyLite authentication
  89. $this->_credentials = new Zend_Service_WindowsAzure_Credentials_SharedKeyLite($accountName, $accountKey, $this->_usePathStyleUri);
  90. // API version
  91. $this->_apiVersion = '2009-09-19';
  92. }
  93. /**
  94. * Check if a table exists
  95. *
  96. * @param string $tableName Table name
  97. * @return boolean
  98. */
  99. public function tableExists($tableName = '')
  100. {
  101. if ($tableName === '') {
  102. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  103. throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.');
  104. }
  105. // List tables
  106. $tables = $this->listTables(); // 2009-09-19 does not support $this->listTables($tableName); all of a sudden...
  107. foreach ($tables as $table) {
  108. if ($table->Name == $tableName) {
  109. return true;
  110. }
  111. }
  112. return false;
  113. }
  114. /**
  115. * List tables
  116. *
  117. * @param string $nextTableName Next table name, used for listing tables when total amount of tables is > 1000.
  118. * @return array
  119. * @throws Zend_Service_WindowsAzure_Exception
  120. */
  121. public function listTables($nextTableName = '')
  122. {
  123. // Build query string
  124. $queryString = array();
  125. if ($nextTableName != '') {
  126. $queryString[] = 'NextTableName=' . $nextTableName;
  127. }
  128. $queryString = self::createQueryStringFromArray($queryString);
  129. // Perform request
  130. $response = $this->_performRequest('Tables', $queryString, Zend_Http_Client::GET, null, true);
  131. if ($response->isSuccessful()) {
  132. // Parse result
  133. $result = $this->_parseResponse($response);
  134. if (!$result || !$result->entry) {
  135. return array();
  136. }
  137. $entries = null;
  138. if (count($result->entry) > 1) {
  139. $entries = $result->entry;
  140. } else {
  141. $entries = array($result->entry);
  142. }
  143. // Create return value
  144. $returnValue = array();
  145. foreach ($entries as $entry) {
  146. $tableName = $entry->xpath('.//m:properties/d:TableName');
  147. $tableName = (string)$tableName[0];
  148. $returnValue[] = new Zend_Service_WindowsAzure_Storage_TableInstance(
  149. (string)$entry->id,
  150. $tableName,
  151. (string)$entry->link['href'],
  152. (string)$entry->updated
  153. );
  154. }
  155. // More tables?
  156. if (!is_null($response->getHeader('x-ms-continuation-NextTableName'))) {
  157. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  158. $returnValue = array_merge($returnValue, $this->listTables($response->getHeader('x-ms-continuation-NextTableName')));
  159. }
  160. return $returnValue;
  161. } else {
  162. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  163. }
  164. }
  165. /**
  166. * Create table
  167. *
  168. * @param string $tableName Table name
  169. * @return Zend_Service_WindowsAzure_Storage_TableInstance
  170. * @throws Zend_Service_WindowsAzure_Exception
  171. */
  172. public function createTable($tableName = '')
  173. {
  174. if ($tableName === '') {
  175. throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.');
  176. }
  177. // Generate request body
  178. $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  179. <entry
  180. xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
  181. xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
  182. xmlns="http://www.w3.org/2005/Atom">
  183. <title />
  184. <updated>{tpl:Updated}</updated>
  185. <author>
  186. <name />
  187. </author>
  188. <id />
  189. <content type="application/xml">
  190. <m:properties>
  191. <d:TableName>{tpl:TableName}</d:TableName>
  192. </m:properties>
  193. </content>
  194. </entry>';
  195. $requestBody = $this->_fillTemplate($requestBody, array(
  196. 'BaseUrl' => $this->getBaseUrl(),
  197. 'TableName' => htmlspecialchars($tableName),
  198. 'Updated' => $this->isoDate(),
  199. 'AccountName' => $this->_accountName
  200. ));
  201. // Add header information
  202. $headers = array();
  203. $headers['Content-Type'] = 'application/atom+xml';
  204. $headers['DataServiceVersion'] = '1.0;NetFx';
  205. $headers['MaxDataServiceVersion'] = '1.0;NetFx';
  206. // Perform request
  207. $response = $this->_performRequest('Tables', '', Zend_Http_Client::POST, $headers, true, $requestBody);
  208. if ($response->isSuccessful()) {
  209. // Parse response
  210. $entry = $this->_parseResponse($response);
  211. $tableName = $entry->xpath('.//m:properties/d:TableName');
  212. $tableName = (string)$tableName[0];
  213. return new Zend_Service_WindowsAzure_Storage_TableInstance(
  214. (string)$entry->id,
  215. $tableName,
  216. (string)$entry->link['href'],
  217. (string)$entry->updated
  218. );
  219. } else {
  220. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  221. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  222. }
  223. }
  224. /**
  225. * Create table if it does not exist
  226. *
  227. * @param string $tableName Table name
  228. * @throws Zend_Service_WindowsAzure_Exception
  229. */
  230. public function createTableIfNotExists($tableName = '')
  231. {
  232. if (!$this->tableExists($tableName)) {
  233. $this->createTable($tableName);
  234. }
  235. }
  236. /**
  237. * Delete table
  238. *
  239. * @param string $tableName Table name
  240. * @throws Zend_Service_WindowsAzure_Exception
  241. */
  242. public function deleteTable($tableName = '')
  243. {
  244. if ($tableName === '') {
  245. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  246. throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.');
  247. }
  248. // Add header information
  249. $headers = array();
  250. $headers['Content-Type'] = 'application/atom+xml';
  251. // Perform request
  252. $response = $this->_performRequest('Tables(\'' . $tableName . '\')', '', Zend_Http_Client::DELETE, $headers, true, null);
  253. if (!$response->isSuccessful()) {
  254. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  255. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  256. }
  257. }
  258. /**
  259. * Insert entity into table
  260. *
  261. * @param string $tableName Table name
  262. * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to insert
  263. * @return Zend_Service_WindowsAzure_Storage_TableEntity
  264. * @throws Zend_Service_WindowsAzure_Exception
  265. */
  266. public function insertEntity($tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null)
  267. {
  268. if ($tableName === '') {
  269. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  270. throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.');
  271. }
  272. if (is_null($entity)) {
  273. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  274. throw new Zend_Service_WindowsAzure_Exception('Entity is not specified.');
  275. }
  276. // Generate request body
  277. $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  278. <entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
  279. <title />
  280. <updated>{tpl:Updated}</updated>
  281. <author>
  282. <name />
  283. </author>
  284. <id />
  285. <content type="application/xml">
  286. <m:properties>
  287. {tpl:Properties}
  288. </m:properties>
  289. </content>
  290. </entry>';
  291. $requestBody = $this->_fillTemplate($requestBody, array(
  292. 'Updated' => $this->isoDate(),
  293. 'Properties' => $this->_generateAzureRepresentation($entity)
  294. ));
  295. // Add header information
  296. $headers = array();
  297. $headers['Content-Type'] = 'application/atom+xml';
  298. // Perform request
  299. $response = null;
  300. if ($this->isInBatch()) {
  301. $this->getCurrentBatch()->enlistOperation($tableName, '', Zend_Http_Client::POST, $headers, true, $requestBody);
  302. return null;
  303. } else {
  304. $response = $this->_performRequest($tableName, '', Zend_Http_Client::POST, $headers, true, $requestBody);
  305. }
  306. if ($response->isSuccessful()) {
  307. // Parse result
  308. $result = $this->_parseResponse($response);
  309. $timestamp = $result->xpath('//m:properties/d:Timestamp');
  310. $timestamp = $this->_convertToDateTime( (string)$timestamp[0] );
  311. $etag = $result->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
  312. $etag = (string)$etag['etag'];
  313. // Update properties
  314. $entity->setTimestamp($timestamp);
  315. $entity->setEtag($etag);
  316. return $entity;
  317. } else {
  318. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  319. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  320. }
  321. }
  322. /**
  323. * Delete entity from table
  324. *
  325. * @param string $tableName Table name
  326. * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to delete
  327. * @param boolean $verifyEtag Verify etag of the entity (used for concurrency)
  328. * @throws Zend_Service_WindowsAzure_Exception
  329. */
  330. public function deleteEntity($tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false)
  331. {
  332. if ($tableName === '') {
  333. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  334. throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.');
  335. }
  336. if (is_null($entity)) {
  337. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  338. throw new Zend_Service_WindowsAzure_Exception('Entity is not specified.');
  339. }
  340. // Add header information
  341. $headers = array();
  342. if (!$this->isInBatch()) {
  343. // http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/9e255447-4dc7-458a-99d3-bdc04bdc5474/
  344. $headers['Content-Type'] = 'application/atom+xml';
  345. }
  346. $headers['Content-Length'] = 0;
  347. if (!$verifyEtag) {
  348. $headers['If-Match'] = '*';
  349. } else {
  350. $headers['If-Match'] = $entity->getEtag();
  351. }
  352. // Perform request
  353. $response = null;
  354. if ($this->isInBatch()) {
  355. $this->getCurrentBatch()->enlistOperation($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', Zend_Http_Client::DELETE, $headers, true, null);
  356. return null;
  357. } else {
  358. $response = $this->_performRequest($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', Zend_Http_Client::DELETE, $headers, true, null);
  359. }
  360. if (!$response->isSuccessful()) {
  361. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  362. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  363. }
  364. }
  365. /**
  366. * Retrieve entity from table, by id
  367. *
  368. * @param string $tableName Table name
  369. * @param string $partitionKey Partition key
  370. * @param string $rowKey Row key
  371. * @param string $entityClass Entity class name*
  372. * @return Zend_Service_WindowsAzure_Storage_TableEntity
  373. * @throws Zend_Service_WindowsAzure_Exception
  374. */
  375. public function retrieveEntityById($tableName, $partitionKey, $rowKey, $entityClass = 'Zend_Service_WindowsAzure_Storage_DynamicTableEntity')
  376. {
  377. if (is_null($tableName) || $tableName === '') {
  378. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  379. throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.');
  380. }
  381. if (is_null($partitionKey) || $partitionKey === '') {
  382. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  383. throw new Zend_Service_WindowsAzure_Exception('Partition key is not specified.');
  384. }
  385. if (is_null($rowKey) || $rowKey === '') {
  386. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  387. throw new Zend_Service_WindowsAzure_Exception('Row key is not specified.');
  388. }
  389. if (is_null($entityClass) || $entityClass === '') {
  390. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  391. throw new Zend_Service_WindowsAzure_Exception('Entity class is not specified.');
  392. }
  393. // Check for combined size of partition key and row key
  394. // http://msdn.microsoft.com/en-us/library/dd179421.aspx
  395. if (strlen($partitionKey . $rowKey) >= 256) {
  396. // Start a batch if possible
  397. if ($this->isInBatch()) {
  398. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  399. throw new Zend_Service_WindowsAzure_Exception('Entity cannot be retrieved. A transaction is required to retrieve the entity, but another transaction is already active.');
  400. }
  401. $this->startBatch();
  402. }
  403. // Fetch entities from Azure
  404. $result = $this->retrieveEntities(
  405. $this->select()
  406. ->from($tableName)
  407. ->wherePartitionKey($partitionKey)
  408. ->whereRowKey($rowKey),
  409. '',
  410. $entityClass
  411. );
  412. // Return
  413. if (count($result) == 1) {
  414. return $result[0];
  415. }
  416. return null;
  417. }
  418. /**
  419. * Create a new Zend_Service_WindowsAzure_Storage_TableEntityQuery
  420. *
  421. * @return Zend_Service_WindowsAzure_Storage_TableEntityQuery
  422. */
  423. public function select()
  424. {
  425. return new Zend_Service_WindowsAzure_Storage_TableEntityQuery();
  426. }
  427. /**
  428. * Retrieve entities from table
  429. *
  430. * @param string $tableName|Zend_Service_WindowsAzure_Storage_TableEntityQuery Table name -or- Zend_Service_WindowsAzure_Storage_TableEntityQuery instance
  431. * @param string $filter Filter condition (not applied when $tableName is a Zend_Service_WindowsAzure_Storage_TableEntityQuery instance)
  432. * @param string $entityClass Entity class name
  433. * @param string $nextPartitionKey Next partition key, used for listing entities when total amount of entities is > 1000.
  434. * @param string $nextRowKey Next row key, used for listing entities when total amount of entities is > 1000.
  435. * @return array Array of Zend_Service_WindowsAzure_Storage_TableEntity
  436. * @throws Zend_Service_WindowsAzure_Exception
  437. */
  438. public function retrieveEntities($tableName = '', $filter = '', $entityClass = 'Zend_Service_WindowsAzure_Storage_DynamicTableEntity', $nextPartitionKey = null, $nextRowKey = null)
  439. {
  440. if ($tableName === '') {
  441. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  442. throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.');
  443. }
  444. if ($entityClass === '') {
  445. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  446. throw new Zend_Service_WindowsAzure_Exception('Entity class is not specified.');
  447. }
  448. // Convenience...
  449. if (class_exists($filter)) {
  450. $entityClass = $filter;
  451. $filter = '';
  452. }
  453. // Query string
  454. $queryString = '';
  455. // Determine query
  456. if (is_string($tableName)) {
  457. // Option 1: $tableName is a string
  458. // Append parentheses
  459. if (strpos($tableName, '()') === false) {
  460. $tableName .= '()';
  461. }
  462. // Build query
  463. $query = array();
  464. // Filter?
  465. if ($filter !== '') {
  466. $query[] = '$filter=' . Zend_Service_WindowsAzure_Storage_TableEntityQuery::encodeQuery($filter);
  467. }
  468. // Build queryString
  469. if (count($query) > 0) {
  470. $queryString = '?' . implode('&', $query);
  471. }
  472. } else if (get_class($tableName) == 'Zend_Service_WindowsAzure_Storage_TableEntityQuery') {
  473. // Option 2: $tableName is a Zend_Service_WindowsAzure_Storage_TableEntityQuery instance
  474. // Build queryString
  475. $queryString = $tableName->assembleQueryString(true);
  476. // Change $tableName
  477. $tableName = $tableName->assembleFrom(true);
  478. } else {
  479. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  480. throw new Zend_Service_WindowsAzure_Exception('Invalid argument: $tableName');
  481. }
  482. // Add continuation querystring parameters?
  483. if (!is_null($nextPartitionKey) && !is_null($nextRowKey)) {
  484. if ($queryString !== '') {
  485. $queryString .= '&';
  486. } else {
  487. $queryString .= '?';
  488. }
  489. $queryString .= 'NextPartitionKey=' . rawurlencode($nextPartitionKey) . '&NextRowKey=' . rawurlencode($nextRowKey);
  490. }
  491. // Perform request
  492. $response = null;
  493. if ($this->isInBatch() && $this->getCurrentBatch()->getOperationCount() == 0) {
  494. $this->getCurrentBatch()->enlistOperation($tableName, $queryString, Zend_Http_Client::GET, array(), true, null);
  495. $response = $this->getCurrentBatch()->commit();
  496. // Get inner response (multipart)
  497. $innerResponse = $response->getBody();
  498. $innerResponse = substr($innerResponse, strpos($innerResponse, 'HTTP/1.1 200 OK'));
  499. $innerResponse = substr($innerResponse, 0, strpos($innerResponse, '--batchresponse'));
  500. $response = Zend_Http_Response::fromString($innerResponse);
  501. } else {
  502. $response = $this->_performRequest($tableName, $queryString, Zend_Http_Client::GET, array(), true, null);
  503. }
  504. if ($response->isSuccessful()) {
  505. // Parse result
  506. $result = $this->_parseResponse($response);
  507. if (!$result) {
  508. return array();
  509. }
  510. $entries = null;
  511. if ($result->entry) {
  512. if (count($result->entry) > 1) {
  513. $entries = $result->entry;
  514. } else {
  515. $entries = array($result->entry);
  516. }
  517. } else {
  518. // This one is tricky... If we have properties defined, we have an entity.
  519. $properties = $result->xpath('//m:properties');
  520. if ($properties) {
  521. $entries = array($result);
  522. } else {
  523. return array();
  524. }
  525. }
  526. // Create return value
  527. $returnValue = array();
  528. foreach ($entries as $entry) {
  529. // Parse properties
  530. $properties = $entry->xpath('.//m:properties');
  531. $properties = $properties[0]->children('http://schemas.microsoft.com/ado/2007/08/dataservices');
  532. // Create entity
  533. $entity = new $entityClass('', '');
  534. $entity->setAzureValues((array)$properties, $this->_throwExceptionOnMissingData);
  535. // If we have a Zend_Service_WindowsAzure_Storage_DynamicTableEntity, make sure all property types are set
  536. if ($entity instanceof Zend_Service_WindowsAzure_Storage_DynamicTableEntity) {
  537. foreach ($properties as $key => $value) {
  538. $attributes = $value->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
  539. $type = (string)$attributes['type'];
  540. if ($type !== '') {
  541. $entity->setAzureProperty($key, (string)$value, $type);
  542. }
  543. }
  544. }
  545. // Update etag
  546. $etag = $entry->attributes('http://schemas.microsoft.com/ado/2007/08/dataservices/metadata');
  547. $etag = (string)$etag['etag'];
  548. $entity->setEtag($etag);
  549. // Add to result
  550. $returnValue[] = $entity;
  551. }
  552. // More entities?
  553. if (!is_null($response->getHeader('x-ms-continuation-NextPartitionKey')) && !is_null($response->getHeader('x-ms-continuation-NextRowKey'))) {
  554. if (strpos($queryString, '$top') === false) {
  555. $returnValue = array_merge($returnValue, $this->retrieveEntities($tableName, $filter, $entityClass, $response->getHeader('x-ms-continuation-NextPartitionKey'), $response->getHeader('x-ms-continuation-NextRowKey')));
  556. }
  557. }
  558. // Return
  559. return $returnValue;
  560. } else {
  561. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  562. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  563. }
  564. }
  565. /**
  566. * Update entity by replacing it
  567. *
  568. * @param string $tableName Table name
  569. * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to update
  570. * @param boolean $verifyEtag Verify etag of the entity (used for concurrency)
  571. * @throws Zend_Service_WindowsAzure_Exception
  572. */
  573. public function updateEntity($tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false)
  574. {
  575. return $this->_changeEntity(Zend_Http_Client::PUT, $tableName, $entity, $verifyEtag);
  576. }
  577. /**
  578. * Update entity by adding or updating properties
  579. *
  580. * @param string $tableName Table name
  581. * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to update
  582. * @param boolean $verifyEtag Verify etag of the entity (used for concurrency)
  583. * @param array $properties Properties to merge. All properties will be used when omitted.
  584. * @throws Zend_Service_WindowsAzure_Exception
  585. */
  586. public function mergeEntity($tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false, $properties = array())
  587. {
  588. $mergeEntity = null;
  589. if (is_array($properties) && count($properties) > 0) {
  590. // Build a new object
  591. $mergeEntity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($entity->getPartitionKey(), $entity->getRowKey());
  592. // Keep only values mentioned in $properties
  593. $azureValues = $entity->getAzureValues();
  594. foreach ($azureValues as $key => $value) {
  595. if (in_array($value->Name, $properties)) {
  596. $mergeEntity->setAzureProperty($value->Name, $value->Value, $value->Type);
  597. }
  598. }
  599. } else {
  600. $mergeEntity = $entity;
  601. }
  602. // Ensure entity timestamp matches updated timestamp
  603. $entity->setTimestamp(new DateTime());
  604. return $this->_changeEntity(Zend_Http_Client::MERGE, $tableName, $mergeEntity, $verifyEtag);
  605. }
  606. /**
  607. * Get error message from Zend_Http_Response
  608. *
  609. * @param Zend_Http_Response $response Repsonse
  610. * @param string $alternativeError Alternative error message
  611. * @return string
  612. */
  613. protected function _getErrorMessage(Zend_Http_Response $response, $alternativeError = 'Unknown error.')
  614. {
  615. $response = $this->_parseResponse($response);
  616. if ($response && $response->message) {
  617. return (string)$response->message;
  618. } else {
  619. return $alternativeError;
  620. }
  621. }
  622. /**
  623. * Update entity / merge entity
  624. *
  625. * @param string $httpVerb HTTP verb to use (PUT = update, MERGE = merge)
  626. * @param string $tableName Table name
  627. * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity Entity to update
  628. * @param boolean $verifyEtag Verify etag of the entity (used for concurrency)
  629. * @throws Zend_Service_WindowsAzure_Exception
  630. */
  631. protected function _changeEntity($httpVerb = Zend_Http_Client::PUT, $tableName = '', Zend_Service_WindowsAzure_Storage_TableEntity $entity = null, $verifyEtag = false)
  632. {
  633. if ($tableName === '') {
  634. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  635. throw new Zend_Service_WindowsAzure_Exception('Table name is not specified.');
  636. }
  637. if (is_null($entity)) {
  638. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  639. throw new Zend_Service_WindowsAzure_Exception('Entity is not specified.');
  640. }
  641. // Add header information
  642. $headers = array();
  643. $headers['Content-Type'] = 'application/atom+xml';
  644. $headers['Content-Length'] = 0;
  645. if (!$verifyEtag) {
  646. $headers['If-Match'] = '*';
  647. } else {
  648. $headers['If-Match'] = $entity->getEtag();
  649. }
  650. // Generate request body
  651. $requestBody = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  652. <entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
  653. <title />
  654. <updated>{tpl:Updated}</updated>
  655. <author>
  656. <name />
  657. </author>
  658. <id />
  659. <content type="application/xml">
  660. <m:properties>
  661. {tpl:Properties}
  662. </m:properties>
  663. </content>
  664. </entry>';
  665. // Attempt to get timestamp from entity
  666. $timestamp = $entity->getTimestamp();
  667. $requestBody = $this->_fillTemplate($requestBody, array(
  668. 'Updated' => $this->_convertToEdmDateTime($timestamp),
  669. 'Properties' => $this->_generateAzureRepresentation($entity)
  670. ));
  671. // Add header information
  672. $headers = array();
  673. $headers['Content-Type'] = 'application/atom+xml';
  674. if (!$verifyEtag) {
  675. $headers['If-Match'] = '*';
  676. } else {
  677. $headers['If-Match'] = $entity->getEtag();
  678. }
  679. // Perform request
  680. $response = null;
  681. if ($this->isInBatch()) {
  682. $this->getCurrentBatch()->enlistOperation($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', $httpVerb, $headers, true, $requestBody);
  683. return null;
  684. } else {
  685. $response = $this->_performRequest($tableName . '(PartitionKey=\'' . $entity->getPartitionKey() . '\', RowKey=\'' . $entity->getRowKey() . '\')', '', $httpVerb, $headers, true, $requestBody);
  686. }
  687. if ($response->isSuccessful()) {
  688. // Update properties
  689. $entity->setEtag($response->getHeader('Etag'));
  690. $entity->setTimestamp( $this->_convertToDateTime($response->getHeader('Last-modified')) );
  691. return $entity;
  692. } else {
  693. #require_once 'Zend/Service/WindowsAzure/Exception.php';
  694. throw new Zend_Service_WindowsAzure_Exception($this->_getErrorMessage($response, 'Resource could not be accessed.'));
  695. }
  696. }
  697. /**
  698. * Generate RFC 1123 compliant date string
  699. *
  700. * @return string
  701. */
  702. protected function _rfcDate()
  703. {
  704. return gmdate('D, d M Y H:i:s', time()) . ' GMT'; // RFC 1123
  705. }
  706. /**
  707. * Fill text template with variables from key/value array
  708. *
  709. * @param string $templateText Template text
  710. * @param array $variables Array containing key/value pairs
  711. * @return string
  712. */
  713. protected function _fillTemplate($templateText, $variables = array())
  714. {
  715. foreach ($variables as $key => $value) {
  716. $templateText = str_replace('{tpl:' . $key . '}', $value, $templateText);
  717. }
  718. return $templateText;
  719. }
  720. /**
  721. * Generate Azure representation from entity (creates atompub markup from properties)
  722. *
  723. * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity
  724. * @return string
  725. */
  726. protected function _generateAzureRepresentation(Zend_Service_WindowsAzure_Storage_TableEntity $entity = null)
  727. {
  728. // Generate Azure representation from entity
  729. $azureRepresentation = array();
  730. $azureValues = $entity->getAzureValues();
  731. foreach ($azureValues as $azureValue) {
  732. $value = array();
  733. $value[] = '<d:' . $azureValue->Name;
  734. if ($azureValue->Type != '') {
  735. $value[] = ' m:type="' . $azureValue->Type . '"';
  736. }
  737. if (is_null($azureValue->Value)) {
  738. $value[] = ' m:null="true"';
  739. }
  740. $value[] = '>';
  741. if (!is_null($azureValue->Value)) {
  742. if (strtolower($azureValue->Type) == 'edm.boolean') {
  743. $value[] = ($azureValue->Value == true ? '1' : '0');
  744. } else if (strtolower($azureValue->Type) == 'edm.datetime') {
  745. $value[] = $this->_convertToEdmDateTime($azureValue->Value);
  746. } else {
  747. $value[] = htmlspecialchars($azureValue->Value);
  748. }
  749. }
  750. $value[] = '</d:' . $azureValue->Name . '>';
  751. $azureRepresentation[] = implode('', $value);
  752. }
  753. return implode('', $azureRepresentation);
  754. }
  755. /**
  756. * Perform request using Zend_Http_Client channel
  757. *
  758. * @param string $path Path
  759. * @param string $queryString Query string
  760. * @param string $httpVerb HTTP verb the request will use
  761. * @param array $headers x-ms headers to add
  762. * @param boolean $forTableStorage Is the request for table storage?
  763. * @param mixed $rawData Optional RAW HTTP data to be sent over the wire
  764. * @param string $resourceType Resource type
  765. * @param string $requiredPermission Required permission
  766. * @return Zend_Http_Response
  767. */
  768. protected function _performRequest(
  769. $path = '/',
  770. $queryString = '',
  771. $httpVerb = Zend_Http_Client::GET,
  772. $headers = array(),
  773. $forTableStorage = false,
  774. $rawData = null,
  775. $resourceType = Zend_Service_WindowsAzure_Storage::RESOURCE_UNKNOWN,
  776. $requiredPermission = Zend_Service_WindowsAzure_Credentials_CredentialsAbstract::PERMISSION_READ
  777. ) {
  778. // Add headers
  779. $headers['DataServiceVersion'] = '1.0;NetFx';
  780. $headers['MaxDataServiceVersion'] = '1.0;NetFx';
  781. // Perform request
  782. return parent::_performRequest(
  783. $path,
  784. $queryString,
  785. $httpVerb,
  786. $headers,
  787. $forTableStorage,
  788. $rawData,
  789. $resourceType,
  790. $requiredPermission
  791. );
  792. }
  793. /**
  794. * Converts a string to a DateTime object. Returns false on failure.
  795. *
  796. * @param string $value The string value to parse
  797. * @return DateTime|boolean
  798. */
  799. protected function _convertToDateTime($value = '')
  800. {
  801. if ($value instanceof DateTime) {
  802. return $value;
  803. }
  804. try {
  805. if (substr($value, -1) == 'Z') {
  806. $value = substr($value, 0, strlen($value) - 1);
  807. }
  808. return new DateTime($value, new DateTimeZone('UTC'));
  809. }
  810. catch (Exception $ex) {
  811. return false;
  812. }
  813. }
  814. /**
  815. * Converts a DateTime object into an Edm.DaeTime value in UTC timezone,
  816. * represented as a string.
  817. *
  818. * @param DateTime $value
  819. * @return string
  820. */
  821. protected function _convertToEdmDateTime(DateTime $value)
  822. {
  823. $cloned = clone $value;
  824. $cloned->setTimezone(new DateTimeZone('UTC'));
  825. return str_replace('+0000', 'Z', $cloned->format(DateTime::ISO8601));
  826. }
  827. }