PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/tags/storage-2009-04-14/library/Microsoft/WindowsAzure/Storage/Table.php

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