PageRenderTime 35ms CodeModel.GetById 2ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 1ms

/WindowsAzure/Table/TableRestProxy.php

http://github.com/WindowsAzure/azure-sdk-for-php
PHP | 1298 lines | 815 code | 138 blank | 345 comment | 50 complexity | 11a23e7c18543be405b656a8bcdbbf09 MD5 | raw file
   1<?php
   2
   3/**
   4 * LICENSE: Licensed under the Apache License, Version 2.0 (the "License");
   5 * you may not use this file except in compliance with the License.
   6 * You may obtain a copy of the License at
   7 * http://www.apache.org/licenses/LICENSE-2.0
   8 *
   9 * Unless required by applicable law or agreed to in writing, software
  10 * distributed under the License is distributed on an "AS IS" BASIS,
  11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12 * See the License for the specific language governing permissions and
  13 * limitations under the License.
  14 * 
  15 * PHP version 5
  16 *
  17 * @category  Microsoft
  18 * @package   WindowsAzure\Table
  19 * @author    Azure PHP SDK <azurephpsdk@microsoft.com>
  20 * @copyright 2012 Microsoft Corporation
  21 * @license   http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
  22 * @link      https://github.com/windowsazure/azure-sdk-for-php
  23 */
  24 
  25namespace WindowsAzure\Table;
  26use WindowsAzure\Common\Internal\Resources;
  27use WindowsAzure\Common\Internal\Utilities;
  28use WindowsAzure\Common\Internal\Validate;
  29use WindowsAzure\Common\Internal\Http\HttpCallContext;
  30use WindowsAzure\Common\Models\ServiceProperties;
  31use WindowsAzure\Common\Internal\ServiceRestProxy;
  32use WindowsAzure\Common\Models\GetServicePropertiesResult;
  33use WindowsAzure\Table\Internal\ITable;
  34use WindowsAzure\Table\Models\TableServiceOptions;
  35use WindowsAzure\Table\Models\EdmType;
  36use WindowsAzure\Table\Models\Filters;
  37use WindowsAzure\Table\Models\Filters\Filter;
  38use WindowsAzure\Table\Models\GetTableResult;
  39use WindowsAzure\Table\Models\QueryTablesOptions;
  40use WindowsAzure\Table\Models\QueryTablesResult;
  41use WindowsAzure\Table\Models\InsertEntityResult;
  42use WindowsAzure\Table\Models\UpdateEntityResult;
  43use WindowsAzure\Table\Models\QueryEntitiesOptions;
  44use WindowsAzure\Table\Models\QueryEntitiesResult;
  45use WindowsAzure\Table\Models\DeleteEntityOptions;
  46use WindowsAzure\Table\Models\GetEntityResult;
  47use WindowsAzure\Table\Models\BatchOperationType;
  48use WindowsAzure\Table\Models\BatchOperationParameterName;
  49use WindowsAzure\Table\Models\BatchResult;
  50
  51/**
  52 * This class constructs HTTP requests and receive HTTP responses for table
  53 * service layer.
  54 *
  55 * @category  Microsoft
  56 * @package   WindowsAzure\Table
  57 * @author    Azure PHP SDK <azurephpsdk@microsoft.com>
  58 * @copyright 2012 Microsoft Corporation
  59 * @license   http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
  60 * @version   Release: @package_version@
  61 * @link      https://github.com/windowsazure/azure-sdk-for-php
  62 */
  63class TableRestProxy extends ServiceRestProxy implements ITable
  64{
  65    /**
  66     * @var Utilities\IAtomReaderWriter
  67     */
  68    private $_atomSerializer;
  69    
  70    /**
  71     *
  72     * @var Utilities\IMimeReaderWriter
  73     */
  74    private $_mimeSerializer;
  75    
  76    /**
  77     * Creates contexts for batch operations.
  78     * 
  79     * @param array $operations The batch operations array.
  80     * 
  81     * @return array
  82     * 
  83     * @throws \InvalidArgumentException 
  84     */
  85    private function _createOperationsContexts($operations)
  86    {
  87        $contexts = array();
  88        
  89        foreach ($operations as $operation) {
  90            $context = null;
  91            $type    = $operation->getType();
  92            
  93            switch ($type) {
  94            case BatchOperationType::INSERT_ENTITY_OPERATION:
  95            case BatchOperationType::UPDATE_ENTITY_OPERATION:
  96            case BatchOperationType::MERGE_ENTITY_OPERATION:
  97            case BatchOperationType::INSERT_REPLACE_ENTITY_OPERATION:
  98            case BatchOperationType::INSERT_MERGE_ENTITY_OPERATION:
  99                $table   = $operation->getParameter(
 100                    BatchOperationParameterName::BP_TABLE
 101                );
 102                $entity  = $operation->getParameter(
 103                    BatchOperationParameterName::BP_ENTITY
 104                );
 105                $context = $this->_getOperationContext($table, $entity, $type);
 106                break;
 107        
 108            case BatchOperationType::DELETE_ENTITY_OPERATION:
 109                $table        = $operation->getParameter(
 110                    BatchOperationParameterName::BP_TABLE
 111                );
 112                $partitionKey = $operation->getParameter(
 113                    BatchOperationParameterName::BP_PARTITION_KEY
 114                );
 115                $rowKey       = $operation->getParameter(
 116                    BatchOperationParameterName::BP_ROW_KEY
 117                );
 118                $etag         = $operation->getParameter(
 119                    BatchOperationParameterName::BP_ETAG
 120                );
 121                $options      = new DeleteEntityOptions();
 122                $options->setEtag($etag);
 123                $context = $this->_constructDeleteEntityContext(
 124                    $table, $partitionKey, $rowKey, $options
 125                );
 126                break;
 127
 128            default:
 129                throw new \InvalidArgumentException();
 130            }
 131            
 132            $contexts[] = $context;
 133        }
 134        
 135        return $contexts;
 136    }
 137    
 138    /**
 139     * Creates operation context for the API.
 140     * 
 141     * @param string        $table  The table name.
 142     * @param Models\Entity $entity The entity object.
 143     * @param string        $type   The API type.
 144     * 
 145     * @return WindowsAzure\Common\Internal\Http\HttpCallContext
 146     * 
 147     * @throws \InvalidArgumentException 
 148     */
 149    private function _getOperationContext($table, $entity, $type)
 150    {
 151        switch ($type) {
 152        case BatchOperationType::INSERT_ENTITY_OPERATION:
 153            return $this->_constructInsertEntityContext($table, $entity, null);
 154            
 155        case BatchOperationType::UPDATE_ENTITY_OPERATION:
 156            return $this->_constructPutOrMergeEntityContext(
 157                $table,
 158                $entity,
 159                Resources::HTTP_PUT,
 160                true,
 161                null
 162            );
 163            
 164        case BatchOperationType::MERGE_ENTITY_OPERATION:
 165            return $this->_constructPutOrMergeEntityContext(
 166                $table,
 167                $entity,
 168                Resources::HTTP_MERGE,
 169                true,
 170                null
 171            );
 172            
 173        case BatchOperationType::INSERT_REPLACE_ENTITY_OPERATION:
 174            return $this->_constructPutOrMergeEntityContext(
 175                $table,
 176                $entity,
 177                Resources::HTTP_PUT,
 178                false,
 179                null
 180            );
 181            
 182        case BatchOperationType::INSERT_MERGE_ENTITY_OPERATION:
 183            return $this->_constructPutOrMergeEntityContext(
 184                $table,
 185                $entity,
 186                Resources::HTTP_MERGE,
 187                false,
 188                null
 189            );
 190        default:
 191            throw new \InvalidArgumentException();
 192        }
 193    }
 194    
 195    /**
 196     * Creates MIME part body for batch API.
 197     * 
 198     * @param array $operations The batch operations.
 199     * @param array $contexts   The contexts objects.
 200     * 
 201     * @return array
 202     * 
 203     * @throws \InvalidArgumentException
 204     */
 205    private function _createBatchRequestBody($operations, $contexts)
 206    {
 207        $mimeBodyParts = array();
 208        $contentId     = 1;
 209        $count         = count($operations);
 210        
 211        Validate::isTrue(
 212            count($operations) == count($contexts),
 213            Resources::INVALID_OC_COUNT_MSG
 214        );
 215        
 216        for ($i = 0; $i < $count; $i++) {
 217            $operation = $operations[$i];
 218            $context   = $contexts[$i];
 219            $type      = $operation->getType();
 220            
 221            switch ($type) {
 222            case BatchOperationType::INSERT_ENTITY_OPERATION:
 223            case BatchOperationType::UPDATE_ENTITY_OPERATION:
 224            case BatchOperationType::MERGE_ENTITY_OPERATION:
 225            case BatchOperationType::INSERT_REPLACE_ENTITY_OPERATION:
 226            case BatchOperationType::INSERT_MERGE_ENTITY_OPERATION:
 227                $contentType  = $context->getHeader(Resources::CONTENT_TYPE);
 228                $body         = $context->getBody();
 229                $contentType .= ';type=entry';
 230                $context->addOptionalHeader(Resources::CONTENT_TYPE, $contentType);
 231                // Use mb_strlen instead of strlen to get the length of the string
 232                // in bytes instead of the length in chars.
 233                $context->addOptionalHeader(
 234                    Resources::CONTENT_LENGTH,
 235                    mb_strlen($body)
 236                );
 237                break;
 238        
 239            case BatchOperationType::DELETE_ENTITY_OPERATION:
 240                break;
 241
 242            default:
 243                throw new \InvalidArgumentException();
 244            }
 245            
 246            $context->addOptionalHeader(Resources::CONTENT_ID, $contentId);
 247            $mimeBodyPart    = $context->__toString();
 248            $mimeBodyParts[] = $mimeBodyPart;
 249            $contentId++;
 250        }
 251        
 252        return $this->_mimeSerializer->encodeMimeMultipart($mimeBodyParts);
 253    }
 254    
 255    /**
 256     * Constructs HTTP call context for deleteEntity API.
 257     * 
 258     * @param string                     $table        The name of the table.
 259     * @param string                     $partitionKey The entity partition key.
 260     * @param string                     $rowKey       The entity row key.
 261     * @param Models\DeleteEntityOptions $options      The optional parameters.
 262     * 
 263     * @return HttpCallContext
 264     */
 265    private function _constructDeleteEntityContext($table, $partitionKey, $rowKey, 
 266        $options
 267    ) {
 268        Validate::isString($table, 'table');
 269        Validate::notNullOrEmpty($table, 'table');
 270        Validate::isTrue(!is_null($partitionKey), Resources::NULL_TABLE_KEY_MSG);
 271        Validate::isTrue(!is_null($rowKey), Resources::NULL_TABLE_KEY_MSG);
 272        
 273        $method      = Resources::HTTP_DELETE;
 274        $headers     = array();
 275        $queryParams = array();
 276        $statusCode  = Resources::STATUS_NO_CONTENT;
 277        $path        = $this->_getEntityPath($table, $partitionKey, $rowKey);
 278        
 279        if (is_null($options)) {
 280            $options = new DeleteEntityOptions();
 281        }
 282        
 283        $etagObj = $options->getEtag();
 284        $Etag    = !is_null($etagObj);
 285        $this->addOptionalQueryParam(
 286            $queryParams,
 287            Resources::QP_TIMEOUT,
 288            $options->getTimeout()
 289        );
 290        $this->addOptionalHeader(
 291            $headers,
 292            Resources::IF_MATCH,
 293            $Etag ? $etagObj : Resources::ASTERISK
 294        );
 295        
 296        $context = new HttpCallContext();
 297        $context->setHeaders($headers);
 298        $context->setMethod($method);
 299        $context->setPath($path);
 300        $context->setQueryParameters($queryParams);
 301        $context->addStatusCode($statusCode);
 302        $context->setUri($this->getUri());
 303        $context->setBody('');
 304        
 305        return $context;
 306    }
 307    
 308    /**
 309     * Constructs HTTP call context for updateEntity, mergeEntity, 
 310     * insertOrReplaceEntity and insertOrMergeEntity.
 311     * 
 312     * @param string                     $table   The table name.
 313     * @param Models\Entity              $entity  The entity instance to use.
 314     * @param string                     $verb    The HTTP method.
 315     * @param boolean                    $useEtag The flag to include etag or not.
 316     * @param Models\TableServiceOptions $options The optional parameters.
 317     * 
 318     * @return HttpCallContext
 319     */
 320    private function _constructPutOrMergeEntityContext($table, $entity, $verb,
 321        $useEtag, $options
 322    ) {
 323        Validate::isString($table, 'table');
 324        Validate::notNullOrEmpty($table, 'table');
 325        Validate::notNullOrEmpty($entity, 'entity');
 326        Validate::isTrue($entity->isValid($msg), $msg);
 327        
 328        $method       = $verb;
 329        $headers      = array();
 330        $queryParams  = array();
 331        $statusCode   = Resources::STATUS_NO_CONTENT;
 332        $partitionKey = $entity->getPartitionKey();
 333        $rowKey       = $entity->getRowKey();
 334        $path         = $this->_getEntityPath($table, $partitionKey, $rowKey);
 335        $body         = $this->_atomSerializer->getEntity($entity);
 336        
 337        if (is_null($options)) {
 338            $options = new TableServiceOptions();
 339        }
 340        
 341        if ($useEtag) {
 342            $etag         = $entity->getEtag();
 343            $ifMatchValue = is_null($etag) ? Resources::ASTERISK : $etag;
 344            
 345            $this->addOptionalHeader($headers, Resources::IF_MATCH, $ifMatchValue);
 346        }
 347        
 348        $this->addOptionalQueryParam(
 349            $queryParams,
 350            Resources::QP_TIMEOUT,
 351            $options->getTimeout()
 352        );
 353        $this->addOptionalHeader(
 354            $headers,
 355            Resources::CONTENT_TYPE,
 356            Resources::XML_ATOM_CONTENT_TYPE
 357        );
 358        
 359        $context = new HttpCallContext();
 360        $context->setBody($body);
 361        $context->setHeaders($headers);
 362        $context->setMethod($method);
 363        $context->setPath($path);
 364        $context->setQueryParameters($queryParams);
 365        $context->addStatusCode($statusCode);
 366        $context->setUri($this->getUri());
 367        
 368        return $context;
 369    }
 370    
 371    /**
 372     * Constructs HTTP call context for insertEntity API.
 373     * 
 374     * @param string                     $table   The name of the table.
 375     * @param Models\Entity              $entity  The table entity.
 376     * @param Models\TableServiceOptions $options The optional parameters.
 377     * 
 378     * @return HttpCallContext
 379     */
 380    private function _constructInsertEntityContext($table, $entity, $options)
 381    {
 382        Validate::isString($table, 'table');
 383        Validate::notNullOrEmpty($table, 'table');
 384        Validate::notNullOrEmpty($entity, 'entity');
 385        Validate::isTrue($entity->isValid($msg), $msg);
 386        
 387        $method      = Resources::HTTP_POST;
 388        $context     = new HttpCallContext();
 389        $headers     = array();
 390        $queryParams = array();
 391        $statusCode  = Resources::STATUS_CREATED;
 392        $path        = $table;
 393        $body        = $this->_atomSerializer->getEntity($entity);
 394        
 395        if (is_null($options)) {
 396            $options = new TableServiceOptions();
 397        }
 398        
 399        $this->addOptionalQueryParam(
 400            $queryParams,
 401            Resources::QP_TIMEOUT,
 402            $options->getTimeout()
 403        );
 404        $this->addOptionalHeader(
 405            $headers,
 406            Resources::CONTENT_TYPE,
 407            Resources::XML_ATOM_CONTENT_TYPE
 408        );
 409        
 410        $context->setBody($body);
 411        $context->setHeaders($headers);
 412        $context->setMethod($method);
 413        $context->setPath($path);
 414        $context->setQueryParameters($queryParams);
 415        $context->addStatusCode($statusCode);
 416        $context->setUri($this->getUri());
 417        
 418        return $context;
 419    }
 420    
 421    /**
 422     * Constructs URI path for entity.
 423     * 
 424     * @param string $table        The table name.
 425     * @param string $partitionKey The entity's partition key.
 426     * @param string $rowKey       The entity's row key.
 427     * 
 428     * @return string 
 429     */
 430    private function _getEntityPath($table, $partitionKey, $rowKey)
 431    {
 432        $encodedPK = $this->_encodeODataUriValue($partitionKey);
 433        $encodedRK = $this->_encodeODataUriValue($rowKey);
 434        
 435        return "$table(PartitionKey='$encodedPK',RowKey='$encodedRK')";
 436    }
 437    
 438    /**
 439     * Does actual work for update and merge entity APIs.
 440     * 
 441     * @param string                     $table   The table name.
 442     * @param Models\Entity              $entity  The entity instance to use.
 443     * @param string                     $verb    The HTTP method.
 444     * @param boolean                    $useEtag The flag to include etag or not.
 445     * @param Models\TableServiceOptions $options The optional parameters.
 446     * 
 447     * @return Models\UpdateEntityResult
 448     */
 449    private function _putOrMergeEntityImpl($table, $entity, $verb, $useEtag,
 450        $options
 451    ) {
 452        $context = $this->_constructPutOrMergeEntityContext(
 453            $table,
 454            $entity,
 455            $verb,
 456            $useEtag,
 457            $options
 458        );
 459        
 460        $response = $this->sendContext($context);
 461        
 462        return UpdateEntityResult::create($response->getHeader());
 463    }
 464 
 465    /**
 466     * Builds filter expression
 467     * 
 468     * @param Filter $filter The filter object
 469     * 
 470     * @return string 
 471     */
 472    private function _buildFilterExpression($filter)
 473    {
 474        $e = Resources::EMPTY_STRING;
 475        $this->_buildFilterExpressionRec($filter, $e);
 476        
 477        return $e;
 478    }
 479    
 480    /**
 481     * Builds filter expression
 482     * 
 483     * @param Filter $filter The filter object
 484     * @param string &$e     The filter expression
 485     * 
 486     * @return string
 487     */
 488    private function _buildFilterExpressionRec($filter, &$e)
 489    {
 490        if (is_null($filter)) {
 491            return;
 492        }
 493        
 494        if ($filter instanceof Filters\PropertyNameFilter) {
 495            $e .= $filter->getPropertyName();
 496        } else if ($filter instanceof Filters\ConstantFilter) {
 497            $value = $filter->getValue();
 498            // If the value is null we just append null regardless of the edmType.
 499            if (is_null($value)) {
 500                $e .= 'null';
 501            } else {
 502                $type = $filter->getEdmType();
 503                $e   .= EdmType::serializeQueryValue($type, $value);
 504            }
 505        } else if ($filter instanceof Filters\UnaryFilter) {
 506            $e .= $filter->getOperator();
 507            $e .= '(';
 508            $this->_buildFilterExpressionRec($filter->getOperand(), $e);
 509            $e .= ')';
 510        } else if ($filter instanceof Filters\BinaryFilter) {
 511            $e .= '(';
 512            $this->_buildFilterExpressionRec($filter->getLeft(), $e);
 513            $e .= ' ';
 514            $e .= $filter->getOperator();
 515            $e .= ' ';
 516            $this->_buildFilterExpressionRec($filter->getRight(), $e);
 517            $e .= ')';
 518        } else if ($filter instanceof Filters\QueryStringFilter) {
 519            $e .= $filter->getQueryString();
 520        }
 521        
 522        return $e;
 523    }
 524    
 525    /**
 526     * Adds query object to the query parameter array
 527     * 
 528     * @param array        $queryParam The URI query parameters 
 529     * @param Models\Query $query      The query object
 530     * 
 531     * @return array 
 532     */
 533    private function _addOptionalQuery($queryParam, $query)
 534    {
 535        if (!is_null($query)) {
 536            $selectedFields = $query->getSelectFields();
 537            if (!empty($selectedFields)) {
 538                $final = $this->_encodeODataUriValues($selectedFields); 
 539                
 540                $this->addOptionalQueryParam(
 541                    $queryParam,
 542                    Resources::QP_SELECT,
 543                    implode(',', $final)
 544                );
 545            }
 546            
 547            if (!is_null($query->getTop())) {
 548                $final = strval($this->_encodeODataUriValue($query->getTop()));
 549                
 550                $this->addOptionalQueryParam(
 551                    $queryParam,
 552                    Resources::QP_TOP,
 553                    $final
 554                );
 555            }
 556            
 557            if (!is_null($query->getFilter())) {
 558                $final = $this->_buildFilterExpression($query->getFilter());
 559                
 560                $this->addOptionalQueryParam(
 561                    $queryParam,
 562                    Resources::QP_FILTER,
 563                    $final
 564                );
 565            }
 566        }
 567        
 568        return $queryParam;
 569    }
 570    
 571    /**
 572     * Encodes OData URI values
 573     * 
 574     * @param array $values The OData URL values
 575     * 
 576     * @return array
 577     */
 578    private function _encodeODataUriValues($values)
 579    {
 580        $list = array();
 581        
 582        foreach ($values as $value) {
 583            $list[] = $this->_encodeODataUriValue($value);
 584        }
 585        
 586        return $list;
 587    }
 588    
 589    /**
 590     * Encodes OData URI value
 591     * 
 592     * @param string $value The OData URL value
 593     * 
 594     * @return string
 595     */
 596    private function _encodeODataUriValue($value)
 597    {
 598        // Replace each single quote (') with double single quotes ('') not doudle
 599        // quotes (")
 600        $value = str_replace('\'', '\'\'', $value);
 601        
 602        // Encode the special URL characters
 603        $value = rawurlencode($value);
 604        
 605        return $value;
 606    }
 607    
 608    /**
 609     * Initializes new TableRestProxy object.
 610     * 
 611     * @param IHttpClient       $channel        The HTTP client channel.
 612     * @param string            $uri            The storage account uri.
 613     * @param IAtomReaderWriter $atomSerializer The atom serializer.
 614     * @param IMimeReaderWriter $mimeSerializer The MIME serializer.
 615     * @param ISerializable     $dataSerializer The data serializer.
 616     */
 617    public function __construct($channel, $uri, $atomSerializer, $mimeSerializer, 
 618        $dataSerializer
 619    ) {
 620        parent::__construct(
 621            $channel,
 622            $uri,
 623            Resources::EMPTY_STRING,
 624            $dataSerializer
 625        );
 626        $this->_atomSerializer = $atomSerializer;
 627        $this->_mimeSerializer = $mimeSerializer;
 628    }
 629    
 630    /**
 631     * Gets the properties of the Table service.
 632     * 
 633     * @param Models\TableServiceOptions $options optional table service options.
 634     * 
 635     * @return WindowsAzure\Common\Models\GetServicePropertiesResult
 636     * 
 637     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452238.aspx
 638     */
 639    public function getServiceProperties($options = null)
 640    {
 641        if (is_null($options)) {
 642            $options = new TableServiceOptions();
 643        }
 644        
 645        $context = new HttpCallContext();
 646        $timeout = $options->getTimeout();
 647        $context->setMethod(Resources::HTTP_GET);
 648        $context->addOptionalQueryParameter(Resources::QP_REST_TYPE, 'service');
 649        $context->addOptionalQueryParameter(Resources::QP_COMP, 'properties');
 650        $context->addOptionalQueryParameter(Resources::QP_TIMEOUT, $timeout);
 651        $context->addStatusCode(Resources::STATUS_OK);
 652        
 653        $response = $this->sendContext($context);
 654        $parsed   = $this->dataSerializer->unserialize($response->getBody());
 655        
 656        return GetServicePropertiesResult::create($parsed);
 657    }
 658
 659    /**
 660     * Sets the properties of the Table service.
 661     * 
 662     * It's recommended to use getServiceProperties, alter the returned object and
 663     * then use setServiceProperties with this altered object.
 664     * 
 665     * @param ServiceProperties          $serviceProperties new service properties
 666     * @param Models\TableServiceOptions $options           optional parameters
 667     * 
 668     * @return none.
 669     * 
 670     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452240.aspx
 671     */
 672    public function setServiceProperties($serviceProperties, $options = null)
 673    {
 674        Validate::isTrue(
 675            $serviceProperties instanceof ServiceProperties,
 676            Resources::INVALID_SVC_PROP_MSG
 677        );
 678        
 679        $method      = Resources::HTTP_PUT;
 680        $headers     = array();
 681        $postParams  = array();
 682        $queryParams = array();
 683        $statusCode  = Resources::STATUS_ACCEPTED;
 684        $path        = Resources::EMPTY_STRING;
 685        $body        = Resources::EMPTY_STRING;
 686        
 687        if (is_null($options)) {
 688            $options = new TableServiceOptions();
 689        }
 690        
 691        $this->addOptionalQueryParam(
 692            $queryParams,
 693            Resources::QP_TIMEOUT,
 694            $options->getTimeout()
 695        );
 696        $this->addOptionalQueryParam(
 697            $queryParams,
 698            Resources::QP_REST_TYPE,
 699            'service'
 700        );
 701        $this->addOptionalQueryParam(
 702            $queryParams,
 703            Resources::QP_COMP,
 704            'properties'
 705        );
 706        
 707        $this->addOptionalHeader(
 708            $headers,
 709            Resources::CONTENT_TYPE,
 710            Resources::XML_ATOM_CONTENT_TYPE
 711        );
 712        $body = $serviceProperties->toXml($this->dataSerializer);
 713        
 714        $this->send(
 715            $method, 
 716            $headers, 
 717            $queryParams, 
 718            $postParams, 
 719            $path, 
 720            $statusCode, 
 721            $body
 722        );
 723    }
 724    
 725    /**
 726     * Quries tables in the given storage account.
 727     * 
 728     * @param Models\QueryTablesOptions|string|Models\Filter $options Could be
 729     * optional parameters, table prefix or filter to apply.
 730     * 
 731     * @return Models\QueryTablesResult
 732     * 
 733     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx
 734     */
 735    public function queryTables($options = null)
 736    {
 737        $method      = Resources::HTTP_GET;
 738        $headers     = array();
 739        $postParams  = array();
 740        $queryParams = array();
 741        $statusCode  = Resources::STATUS_OK;
 742        $path        = 'Tables';
 743        
 744        if (is_null($options)) {
 745            $options = new QueryTablesOptions();
 746        } else if (is_string($options)) {
 747            $prefix  = $options;
 748            $options = new QueryTablesOptions();
 749            $options->setPrefix($prefix);
 750        } else if ($options instanceof Filter) {
 751            $filter  = $options;
 752            $options = new QueryTablesOptions();
 753            $options->setFilter($filter);
 754        }
 755        
 756        $query   = $options->getQuery();
 757        $next    = $options->getNextTableName();
 758        $prefix  = $options->getPrefix();
 759        $timeout = $options->getTimeout();
 760        
 761        if (!empty($prefix)) {
 762            // Append Max char to end '{' is 1 + 'z' in AsciiTable ==> upperBound 
 763            // is prefix + '{'
 764            $prefixFilter = Filter::applyAnd(
 765                Filter::applyGe(
 766                    Filter::applyPropertyName('TableName'),
 767                    Filter::applyConstant($prefix, EdmType::STRING)
 768                ),
 769                Filter::applyLe(
 770                    Filter::applyPropertyName('TableName'),
 771                    Filter::applyConstant($prefix . '{', EdmType::STRING)
 772                )
 773            );
 774            
 775            if (is_null($query)) {
 776                $query = new Models\Query();
 777            }
 778
 779            if (is_null($query->getFilter())) {
 780                // use the prefix filter if the query filter is null
 781                $query->setFilter($prefixFilter);
 782            } else {
 783                // combine and use the prefix filter if the query filter exists
 784                $combinedFilter = Filter::applyAnd(
 785                    $query->getFilter(), $prefixFilter
 786                );
 787                $query->setFilter($combinedFilter);
 788            }
 789        }
 790        
 791        $queryParams = $this->_addOptionalQuery($queryParams, $query);
 792        
 793        $this->addOptionalQueryParam(
 794            $queryParams,
 795            Resources::QP_NEXT_TABLE_NAME,
 796            $next
 797        );
 798        $this->addOptionalQueryParam(
 799            $queryParams,
 800            Resources::QP_TIMEOUT,
 801            $timeout
 802        );
 803        
 804        // One can specify the NextTableName option to get table entities starting 
 805        // from the specified name. However, there appears to be an issue in the 
 806        // Azure Table service where this does not engage on the server unless 
 807        // $filter appears in the URL. The current behavior is to just ignore the 
 808        // NextTableName options, which is not expected or easily detectable.
 809        if (   array_key_exists(Resources::QP_NEXT_TABLE_NAME, $queryParams)
 810            && !array_key_exists(Resources::QP_FILTER, $queryParams)
 811        ) {
 812            $queryParams[Resources::QP_FILTER] = Resources::EMPTY_STRING;
 813        }
 814        
 815        $response = $this->send(
 816            $method, 
 817            $headers, 
 818            $queryParams, 
 819            $postParams, 
 820            $path, 
 821            $statusCode
 822        );
 823        $tables   = $this->_atomSerializer->parseTableEntries($response->getBody());
 824        
 825        return QueryTablesResult::create($response->getHeader(), $tables);
 826    }
 827    
 828    /**
 829     * Creates new table in the storage account
 830     * 
 831     * @param string                     $table   The name of the table.
 832     * @param Models\TableServiceOptions $options The optional parameters.
 833     * 
 834     * @return none
 835     * 
 836     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx
 837     */
 838    public function createTable($table, $options = null)
 839    {
 840        Validate::isString($table, 'table');
 841        Validate::notNullOrEmpty($table, 'table');
 842        
 843        $method      = Resources::HTTP_POST;
 844        $headers     = array();
 845        $postParams  = array();
 846        $queryParams = array();
 847        $statusCode  = Resources::STATUS_CREATED;
 848        $path        = 'Tables';
 849        $body        = $this->_atomSerializer->getTable($table);
 850        
 851        if (is_null($options)) {
 852            $options = new TableServiceOptions();
 853        }
 854        
 855        $this->addOptionalHeader(
 856            $headers,
 857            Resources::CONTENT_TYPE,
 858            Resources::XML_ATOM_CONTENT_TYPE
 859        );
 860        $this->addOptionalQueryParam(
 861            $queryParams,
 862            Resources::QP_TIMEOUT,
 863            $options->getTimeout()
 864        );
 865        
 866        $this->send(
 867            $method, 
 868            $headers, 
 869            $queryParams, 
 870            $postParams, 
 871            $path, 
 872            $statusCode, 
 873            $body
 874        );
 875    }
 876    
 877    /**
 878     * Gets the table.
 879     * 
 880     * @param string                     $table   The name of the table.
 881     * @param Models\TableServiceOptions $options The optional parameters.
 882     * 
 883     * @return Models\GetTableResult
 884     */
 885    public function getTable($table, $options = null)
 886    {
 887        Validate::isString($table, 'table');
 888        Validate::notNullOrEmpty($table, 'table');
 889        
 890        $method      = Resources::HTTP_GET;
 891        $headers     = array();
 892        $postParams  = array();
 893        $queryParams = array();
 894        $statusCode  = Resources::STATUS_OK;
 895        $path        = "Tables('$table')";
 896        
 897        if (is_null($options)) {
 898            $options = new TableServiceOptions();
 899        }
 900        
 901        $this->addOptionalHeader(
 902            $headers,
 903            Resources::CONTENT_TYPE,
 904            Resources::XML_ATOM_CONTENT_TYPE
 905        );
 906        $this->addOptionalQueryParam(
 907            $queryParams,
 908            Resources::QP_TIMEOUT,
 909            $options->getTimeout()
 910        );
 911        
 912        $response = $this->send(
 913            $method, 
 914            $headers, 
 915            $queryParams, 
 916            $postParams, 
 917            $path, 
 918            $statusCode
 919        );
 920        
 921        return GetTableResult::create($response->getBody(), $this->_atomSerializer);
 922    }
 923    
 924    /**
 925     * Deletes the specified table and any data it contains.
 926     * 
 927     * @param string                     $table   The name of the table.
 928     * @param Models\TableServiceOptions $options optional parameters
 929     * 
 930     * @return none
 931     * 
 932     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx
 933     */
 934    public function deleteTable($table, $options = null)
 935    {
 936        Validate::isString($table, 'table');
 937        Validate::notNullOrEmpty($table, 'table');
 938        
 939        $method      = Resources::HTTP_DELETE;
 940        $headers     = array();
 941        $postParams  = array();
 942        $queryParams = array();
 943        $statusCode  = Resources::STATUS_NO_CONTENT;
 944        $path        = "Tables('$table')";
 945        
 946        if (is_null($options)) {
 947            $options = new TableServiceOptions();
 948        }
 949        
 950        $this->addOptionalQueryParam(
 951            $queryParams,
 952            Resources::QP_TIMEOUT,
 953            $options->getTimeout()
 954        );
 955        
 956        $this->send(
 957            $method, 
 958            $headers, 
 959            $queryParams, 
 960            $postParams, 
 961            $path, 
 962            $statusCode
 963        );
 964    }
 965    
 966    /**
 967     * Quries entities for the given table name
 968     * 
 969     * @param string                                           $table   The name of
 970     * the table.
 971     * @param Models\QueryEntitiesOptions|string|Models\Filter $options Coule be
 972     * optional parameters, query string or filter to apply.
 973     * 
 974     * @return Models\QueryEntitiesResult
 975     * 
 976     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx
 977     */
 978    public function queryEntities($table, $options = null)
 979    {
 980        Validate::isString($table, 'table');
 981        Validate::notNullOrEmpty($table, 'table');
 982        
 983        $method      = Resources::HTTP_GET;
 984        $headers     = array();
 985        $postParams  = array();
 986        $queryParams = array();
 987        $statusCode  = Resources::STATUS_OK;
 988        $path        = $table;
 989        
 990        if (is_null($options)) {
 991            $options = new QueryEntitiesOptions();
 992        } else if (is_string($options)) {
 993            $queryString = $options;
 994            $options     = new QueryEntitiesOptions();
 995            $options->setFilter(Filter::applyQueryString($queryString));
 996        } else if ($options instanceof Filter) {
 997            $filter  = $options;
 998            $options = new QueryEntitiesOptions();
 999            $options->setFilter($filter);
1000        }
1001        
1002        $encodedPK   = $this->_encodeODataUriValue($options->getNextPartitionKey());
1003        $encodedRK   = $this->_encodeODataUriValue($options->getNextRowKey());
1004        $queryParams = $this->_addOptionalQuery($queryParams, $options->getQuery());
1005        
1006        $this->addOptionalQueryParam(
1007            $queryParams,
1008            Resources::QP_TIMEOUT,
1009            $options->getTimeout()
1010        );
1011        $this->addOptionalQueryParam(
1012            $queryParams,
1013            Resources::QP_NEXT_PK,
1014            $encodedPK
1015        );
1016        $this->addOptionalQueryParam(
1017            $queryParams,
1018            Resources::QP_NEXT_RK,
1019            $encodedRK
1020        );
1021        
1022        $this->addOptionalHeader(
1023            $headers,
1024            Resources::CONTENT_TYPE,
1025            Resources::XML_ATOM_CONTENT_TYPE
1026        );
1027        
1028        if (!is_null($options->getQuery())) {
1029            $dsHeader   = Resources::DATA_SERVICE_VERSION;
1030            $maxdsValue = Resources::MAX_DATA_SERVICE_VERSION_VALUE;
1031            $fields     = $options->getQuery()->getSelectFields();
1032            $hasSelect  = !empty($fields);
1033            if ($hasSelect) {
1034                $this->addOptionalHeader($headers, $dsHeader, $maxdsValue);
1035            }
1036        }
1037        
1038        $response = $this->send(
1039            $method,
1040            $headers, 
1041            $queryParams, 
1042            $postParams, 
1043            $path, 
1044            $statusCode
1045        );
1046
1047        $entities = $this->_atomSerializer->parseEntities($response->getBody());
1048        
1049        return QueryEntitiesResult::create($response->getHeader(), $entities);
1050    }
1051    
1052    /**
1053     * Inserts new entity to the table.
1054     * 
1055     * @param string                     $table   name of the table.
1056     * @param Models\Entity              $entity  table entity.
1057     * @param Models\TableServiceOptions $options optional parameters.
1058     * 
1059     * @return Models\InsertEntityResult
1060     * 
1061     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179433.aspx
1062     */
1063    public function insertEntity($table, $entity, $options = null)
1064    {
1065        $context = $this->_constructInsertEntityContext($table, $entity, $options);
1066        
1067        $response = $this->sendContext($context);
1068        $body     = $response->getBody();
1069        $headers  = $response->getHeader();
1070        
1071        return InsertEntityResult::create($body, $headers, $this->_atomSerializer);
1072    }
1073    
1074    /**
1075     * Updates an existing entity or inserts a new entity if it does not exist in the
1076     * table.
1077     * 
1078     * @param string                     $table   name of the table
1079     * @param Models\Entity              $entity  table entity
1080     * @param Models\TableServiceOptions $options optional parameters
1081     * 
1082     * @return Models\UpdateEntityResult
1083     * 
1084     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452241.aspx
1085     */
1086    public function insertOrMergeEntity($table, $entity, $options = null)
1087    {
1088        return $this->_putOrMergeEntityImpl(
1089            $table,
1090            $entity,
1091            Resources::HTTP_MERGE,
1092            false, 
1093            $options
1094        );
1095    }
1096    
1097    /**
1098     * Replaces an existing entity or inserts a new entity if it does not exist in
1099     * the table.
1100     * 
1101     * @param string                     $table   name of the table
1102     * @param Models\Entity              $entity  table entity
1103     * @param Models\TableServiceOptions $options optional parameters
1104     * 
1105     * @return Models\UpdateEntityResult
1106     * 
1107     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452242.aspx
1108     */
1109    public function insertOrReplaceEntity($table, $entity, $options = null)
1110    {
1111        return $this->_putOrMergeEntityImpl(
1112            $table,
1113            $entity,
1114            Resources::HTTP_PUT,
1115            false, 
1116            $options
1117        );
1118    }
1119    
1120    /**
1121     * Updates an existing entity in a table. The Update Entity operation replaces 
1122     * the entire entity and can be used to remove properties.
1123     * 
1124     * @param string                     $table   The table name.
1125     * @param Models\Entity              $entity  The table entity.
1126     * @param Models\TableServiceOptions $options The optional parameters.
1127     * 
1128     * @return Models\UpdateEntityResult
1129     * 
1130     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179427.aspx
1131     */
1132    public function updateEntity($table, $entity, $options = null)
1133    {
1134        return $this->_putOrMergeEntityImpl(
1135            $table,
1136            $entity,
1137            Resources::HTTP_PUT,
1138            true, 
1139            $options
1140        );
1141    }
1142    
1143    /**
1144     * Updates an existing entity by updating the entity's properties. This operation
1145     * does not replace the existing entity, as the updateEntity operation does.
1146     * 
1147     * @param string                     $table   The table name.
1148     * @param Models\Entity              $entity  The table entity.
1149     * @param Models\TableServiceOptions $options The optional parameters.
1150     * 
1151     * @return Models\UpdateEntityResult
1152     * 
1153     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179392.aspx
1154     */
1155    public function mergeEntity($table, $entity, $options = null)
1156    {
1157        return $this->_putOrMergeEntityImpl(
1158            $table,
1159            $entity,
1160            Resources::HTTP_MERGE,
1161            true, 
1162            $options
1163        );
1164    }
1165    
1166    /**
1167     * Deletes an existing entity in a table.
1168     * 
1169     * @param string                     $table        The name of the table.
1170     * @param string                     $partitionKey The entity partition key.
1171     * @param string                     $rowKey       The entity row key.
1172     * @param Models\DeleteEntityOptions $options      The optional parameters.
1173     * 
1174     * @return none
1175     * 
1176     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd135727.aspx
1177     */
1178    public function deleteEntity($table, $partitionKey, $rowKey, $options = null)
1179    {
1180        $context = $this->_constructDeleteEntityContext(
1181            $table,
1182            $partitionKey,
1183            $rowKey,
1184            $options
1185        );
1186        
1187        $this->sendContext($context);
1188    }
1189    
1190    /**
1191     * Gets table entity.
1192     * 
1193     * @param string                     $table        The name of the table.
1194     * @param string                     $partitionKey The entity partition key.
1195     * @param string                     $rowKey       The entity row key.
1196     * @param Models\TableServiceOptions $options      The optional parameters.
1197     * 
1198     * @return Models\GetEntityResult
1199     * 
1200     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx
1201     */
1202    public function getEntity($table, $partitionKey, $rowKey, $options = null)
1203    {
1204        Validate::isString($table, 'table');
1205        Validate::notNullOrEmpty($table, 'table');
1206        Validate::isTrue(!is_null($partitionKey), Resources::NULL_TABLE_KEY_MSG);
1207        Validate::isTrue(!is_null($rowKey), Resources::NULL_TABLE_KEY_MSG);
1208        
1209        $method      = Resources::HTTP_GET;
1210        $headers     = array();
1211        $queryParams = array();
1212        $statusCode  = Resources::STATUS_OK;
1213        $path        = $this->_getEntityPath($table, $partitionKey, $rowKey);
1214        
1215        if (is_null($options)) {
1216            $options = new TableServiceOptions();
1217        }
1218        
1219        $this->addOptionalHeader(
1220            $headers,
1221            Resources::CONTENT_TYPE,
1222            Resources::XML_ATOM_CONTENT_TYPE
1223        );
1224        $this->addOptionalQueryParam(
1225            $queryParams,
1226            Resources::QP_TIMEOUT,
1227            $options->getTimeout()
1228        );
1229        
1230        $context = new HttpCallContext();
1231        $context->setHeaders($headers);
1232        $context->setMethod($method);
1233        $context->setPath($path);
1234        $context->setQueryParameters($queryParams);
1235        $context->addStatusCode($statusCode);
1236        
1237        $response = $this->sendContext($context);
1238        $entity   = $this->_atomSerializer->parseEntity($response->getBody());
1239        $result   = new GetEntityResult();
1240        $result->setEntity($entity);
1241        
1242        return $result;
1243    }
1244    
1245    /**
1246     * Does batch of operations on the table service.
1247     * 
1248     * @param Models\BatchOperations     $batchOperations The operations to apply.
1249     * @param Models\TableServiceOptions $options         The optional parameters.
1250     * 
1251     * @return Models\BatchResult
1252     */
1253    public function batch($batchOperations, $options = null)
1254    {
1255        Validate::notNullOrEmpty($batchOperations, 'batchOperations');
1256        
1257        $method      = Resources::HTTP_POST;
1258        $operations  = $batchOperations->getOperations();
1259        $contexts    = $this->_createOperationsContexts($operations);
1260        $mime        = $this->_createBatchRequestBody($operations, $contexts);
1261        $body        = $mime['body'];
1262        $headers     = $mime['headers'];
1263        $postParams  = array();
1264        $queryParams = array();
1265        $statusCode  = Resources::STATUS_ACCEPTED;
1266        $path        = '$batch';
1267        
1268        if (is_null($options)) {
1269            $options = new TableServiceOptions();
1270        }
1271        
1272        $this->addOptionalQueryParam(
1273            $queryParams,
1274            Resources::QP_TIMEOUT,
1275            $options->getTimeout()
1276        );
1277        
1278        $response = $this->send(
1279            $method, 
1280            $headers, 
1281            $queryParams, 
1282            $postParams, 
1283            $path, 
1284            $statusCode, 
1285            $body
1286        );
1287        
1288        return BatchResult::create(
1289            $response->getBody(),
1290            $operations,
1291            $contexts,
1292            $this->_atomSerializer,
1293            $this->_mimeSerializer
1294        );
1295    }
1296}
1297
1298?>