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

/trunk/MoodleWebRole/azure/Microsoft/WindowsAzure/Storage/Table.php

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