PageRenderTime 73ms CodeModel.GetById 13ms app.highlight 52ms RepoModel.GetById 1ms app.codeStats 0ms

/codes-php/phpjakarta/WindowsAzure/Table/TableRestProxy.php

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