PageRenderTime 336ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Service/WindowsAzure/Storage/Table.php

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