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