PageRenderTime 56ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/profiles/acquia/modules/azure/phpazure/library/Microsoft/WindowsAzure/Storage/Table.php

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