PageRenderTime 47ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

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

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