/**
   * Reserved for internal use. Perform a merge operation on the specified table, using the
   * specified {@link TableRequestOptions} and {@link OperationContext}.
   *
   * <p>This method will invoke the Merge Entity REST API to execute this table operation, using the
   * Table service endpoint and storage account credentials in the {@link CloudTableClient} object.
   *
   * @param client A {@link CloudTableClient} instance specifying the Table service endpoint,
   *     storage account credentials, and any additional query parameters.
   * @param tableName A <code>String</code> containing the name of the table.
   * @param options A {@link TableRequestOptions} object that specifies execution options such as
   *     retry policy and timeout settings for the operation.
   * @param opContext An {@link OperationContext} object for tracking the current operation.
   * @return A {@link TableResult} containing the results of executing the operation.
   * @throws StorageException if an error occurs in the storage operation.
   */
  private TableResult performMerge(
      final CloudTableClient client,
      final String tableName,
      final TableRequestOptions options,
      final OperationContext opContext)
      throws StorageException {
    Utility.assertNotNullOrEmpty("Merge requires a valid ETag", this.getEntity().getEtag());
    Utility.assertNotNullOrEmpty(
        "Merge requires a valid PartitionKey", this.getEntity().getPartitionKey());
    Utility.assertNotNullOrEmpty("Merge requires a valid RowKey", this.getEntity().getRowKey());

    final StorageOperation<CloudTableClient, TableOperation, TableResult> impl =
        new StorageOperation<CloudTableClient, TableOperation, TableResult>(options) {
          @Override
          public TableResult execute(
              final CloudTableClient client,
              final TableOperation operation,
              final OperationContext opContext)
              throws Exception {

            final HttpURLConnection request =
                TableRequest.merge(
                    client.getTransformedEndPoint(opContext),
                    tableName,
                    generateRequestIdentity(false, null, false),
                    operation.getEntity().getEtag(),
                    options.getTimeoutIntervalInMs(),
                    null,
                    options,
                    opContext);

            client.getCredentials().signRequestLite(request, -1L, opContext);

            AtomPubParser.writeSingleEntityToStream(
                operation.getEntity(), false, request.getOutputStream(), opContext);

            this.setResult(ExecutionEngine.processRequest(request, opContext));

            if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND
                || this.getResult().getStatusCode() == HttpURLConnection.HTTP_CONFLICT) {
              throw TableServiceException.generateTableServiceException(
                  false, this.getResult(), operation, request.getErrorStream());
            }

            if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) {
              return operation.parseResponse(
                  null,
                  this.getResult().getStatusCode(),
                  request.getHeaderField(TableConstants.HeaderConstants.ETAG),
                  opContext);
            } else {
              throw TableServiceException.generateTableServiceException(
                  true, this.getResult(), operation, request.getErrorStream());
            }
          }
        };

    return ExecutionEngine.executeWithRetry(
        client, this, impl, options.getRetryPolicyFactory(), opContext);
  }
  /**
   * Gets a result segment containing a collection of queues from the storage service with the
   * specified parameters.
   *
   * @param prefix A <code>String</code> containing the queue name prefix to filter the results
   *     with.
   * @param detailsIncluded A {@link QueueListingDetails} value that indicates whether queue
   *     metadata will be returned.
   * @param maxResults The maximum number of queue results to retrieve.
   * @param continuationToken A {@link ResultContinuation} object that represents a continuation
   *     token returned by a previous listing operation.
   * @param options A {@link QueueRequestOptions} object that specifies any additional options for
   *     the request. Specifying <code>null</code> will use the default request options from the
   *     associated service client ( {@link CloudQueue}).
   * @param taskReference A {@link StorageOperation} reference to the encapsulating task.
   * @param opContext An {@link OperationContext} object that represents the context for the current
   *     operation. This object is used to track requests to the storage service, and to provide
   *     additional runtime information about the operation.
   * @return A {@link ResultSegment} of {@link CloudQueue} objects that contains a segment of the
   *     iterable collection of {@link CloudQueue} objects that represent the requested queues in
   *     the storage service.
   * @throws IOException
   * @throws URISyntaxException If the URI is not valid.
   * @throws XMLStreamException
   * @throws InvalidKeyException
   * @throws StorageException If a storage service error occurred during the operation.
   */
  @DoesServiceRequest
  ResultSegment<CloudQueue> listQueuesCore(
      final String prefix,
      final QueueListingDetails detailsIncluded,
      final int maxResults,
      final ResultContinuation continuationToken,
      final RequestOptions options,
      final StorageOperation<CloudQueueClient, Void, ResultSegment<CloudQueue>> taskReference,
      final OperationContext opContext)
      throws IOException, URISyntaxException, XMLStreamException, InvalidKeyException,
          StorageException {

    Utility.assertContinuationType(continuationToken, ResultContinuationType.QUEUE);

    final ListingContext listingContext = new ListingContext(prefix, maxResults);
    listingContext.setMarker(continuationToken != null ? continuationToken.getNextMarker() : null);

    final HttpURLConnection listQueueRequest =
        QueueRequest.list(
            this.getEndpoint(),
            options.getTimeoutIntervalInMs(),
            listingContext,
            detailsIncluded,
            opContext);

    this.getCredentials().signRequest(listQueueRequest, -1L);

    taskReference.setResult(ExecutionEngine.processRequest(listQueueRequest, opContext));

    if (taskReference.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
      taskReference.setNonExceptionedRetryableFailure(true);
      return null;
    }

    final ListQueuesResponse response = new ListQueuesResponse(listQueueRequest.getInputStream());
    response.parseResponse(this);

    ResultContinuation newToken = null;

    if (response.getNextMarker() != null) {
      newToken = new ResultContinuation();
      newToken.setNextMarker(response.getNextMarker());
      newToken.setContinuationType(ResultContinuationType.QUEUE);
    }

    final ResultSegment<CloudQueue> resSegment =
        new ResultSegment<CloudQueue>(response.getQueues(this), maxResults, newToken);

    return resSegment;
  }
  /**
   * Gets a result segment of an iterable collection of queues whose names begin with the specified
   * prefix for this queue, using the specified listing details options, request options, and
   * operation context.
   *
   * @param prefix A <code>String</code> that represents the prefix of the queue name to match.
   * @param detailsIncluded A {@link QueueListingDetails} value that indicates whether queue
   *     metadata will be returned.
   * @param maxResults The maximum number of queue results to retrieve.
   * @param continuationToken A {@link ResultContinuation} object that represents a continuation
   *     token returned by a previous listing operation.
   * @param options A {@link QueueRequestOptions} object that specifies any additional options for
   *     the request. Specifying <code>null</code> will use the default request options from the
   *     associated service client ( {@link CloudQueue}).
   * @param opContext An {@link OperationContext} object that represents the context for the current
   *     operation. This object is used to track requests to the storage service, and to provide
   *     additional runtime information about the operation.
   * @return A {@link ResultSegment} of {@link CloudQueue} objects that contains a segment of the
   *     iterable collection of {@link CloudQueue} objects that represent the requested queues in
   *     the storage service.
   * @throws StorageException If a storage service error occurred during the operation.
   */
  @DoesServiceRequest
  public ResultSegment<CloudQueue> listQueuesSegmented(
      final String prefix,
      final QueueListingDetails detailsIncluded,
      final int maxResults,
      final ResultContinuation continuationToken,
      QueueRequestOptions options,
      OperationContext opContext)
      throws StorageException {

    if (opContext == null) {
      opContext = new OperationContext();
    }

    if (options == null) {
      options = new QueueRequestOptions();
    }

    opContext.initialize();
    options.applyDefaults(this);

    Utility.assertContinuationType(continuationToken, ResultContinuationType.QUEUE);

    final StorageOperation<CloudQueueClient, Void, ResultSegment<CloudQueue>> impl =
        new StorageOperation<CloudQueueClient, Void, ResultSegment<CloudQueue>>(options) {
          @Override
          public ResultSegment<CloudQueue> execute(
              final CloudQueueClient client, final Void dontCare, final OperationContext opContext)
              throws Exception {
            return CloudQueueClient.this.listQueuesCore(
                prefix,
                detailsIncluded,
                maxResults,
                continuationToken,
                this.getRequestOptions(),
                this,
                opContext);
          }
        };

    return ExecutionEngine.executeWithRetry(
        this, null, impl, options.getRetryPolicyFactory(), opContext);
  }
  /**
   * Reserved for internal use. Performs an insert operation on the specified table, using the
   * specified {@link TableRequestOptions} and {@link OperationContext}.
   *
   * <p>This method will invoke the Insert Entity REST API to execute this table operation, using
   * the Table service endpoint and storage account credentials in the {@link CloudTableClient}
   * object.
   *
   * @param client A {@link CloudTableClient} instance specifying the Table service endpoint,
   *     storage account credentials, and any additional query parameters.
   * @param tableName A <code>String</code> containing the name of the table.
   * @param options A {@link TableRequestOptions} object that specifies execution options such as
   *     retry policy and timeout settings for the operation.
   * @param opContext An {@link OperationContext} object for tracking the current operation.
   * @return A {@link TableResult} containing the results of executing the operation.
   * @throws StorageException if an error occurs in the storage operation.
   */
  private TableResult performInsert(
      final CloudTableClient client,
      final String tableName,
      final TableRequestOptions options,
      final OperationContext opContext)
      throws StorageException {
    final boolean isTableEntry = TableConstants.TABLES_SERVICE_TABLES_NAME.equals(tableName);
    final String tableIdentity =
        isTableEntry
            ? this.getEntity()
                .writeEntity(opContext)
                .get(TableConstants.TABLE_NAME)
                .getValueAsString()
            : null;

    // Upserts need row key and partition key
    if (!isTableEntry && this.opType != TableOperationType.INSERT) {
      Utility.assertNotNullOrEmpty(
          "Upserts require a valid PartitionKey", this.getEntity().getPartitionKey());
      Utility.assertNotNullOrEmpty("Upserts require a valid RowKey", this.getEntity().getRowKey());
    }

    final StorageOperation<CloudTableClient, TableOperation, TableResult> impl =
        new StorageOperation<CloudTableClient, TableOperation, TableResult>(options) {
          @Override
          public TableResult execute(
              final CloudTableClient client,
              final TableOperation operation,
              final OperationContext opContext)
              throws Exception {
            final HttpURLConnection request =
                TableRequest.insert(
                    client.getTransformedEndPoint(opContext),
                    tableName,
                    generateRequestIdentity(isTableEntry, tableIdentity, false),
                    operation.opType != TableOperationType.INSERT
                        ? operation.getEntity().getEtag()
                        : null,
                    operation.opType.getUpdateType(),
                    options.getTimeoutIntervalInMs(),
                    null,
                    options,
                    opContext);

            client.getCredentials().signRequestLite(request, -1L, opContext);

            AtomPubParser.writeSingleEntityToStream(
                operation.getEntity(), isTableEntry, request.getOutputStream(), opContext);

            this.setResult(ExecutionEngine.processRequest(request, opContext));
            if (operation.opType == TableOperationType.INSERT) {
              if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_CONFLICT) {
                throw TableServiceException.generateTableServiceException(
                    false, this.getResult(), operation, request.getErrorStream());
              }

              if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) {
                throw TableServiceException.generateTableServiceException(
                    true, this.getResult(), operation, request.getErrorStream());
              }

              InputStream inStream = request.getInputStream();
              TableResult res = null;

              try {
                final XMLStreamReader xmlr = Utility.createXMLStreamReaderFromStream(inStream);
                res =
                    operation.parseResponse(
                        xmlr, this.getResult().getStatusCode(), null, opContext);
              } finally {
                inStream.close();
              }

              return res;
            } else {
              if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) {
                return operation.parseResponse(
                    null,
                    this.getResult().getStatusCode(),
                    request.getHeaderField(TableConstants.HeaderConstants.ETAG),
                    opContext);
              } else {
                throw TableServiceException.generateTableServiceException(
                    true, this.getResult(), operation, request.getErrorStream());
              }
            }
          }
        };

    return ExecutionEngine.executeWithRetry(
        client, this, impl, options.getRetryPolicyFactory(), opContext);
  }
  /**
   * Reserved for internal use. Performs a delete operation on the specified table, using the
   * specified {@link TableRequestOptions} and {@link OperationContext}.
   *
   * <p>This method will invoke the <a
   * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135727.aspx">Delete Entity</a>
   * REST API to execute this table operation, using the Table service endpoint and storage account
   * credentials in the {@link CloudTableClient} object.
   *
   * @param client A {@link CloudTableClient} instance specifying the Table service endpoint,
   *     storage account credentials, and any additional query parameters.
   * @param tableName A <code>String</code> containing the name of the table.
   * @param options A {@link TableRequestOptions} object that specifies execution options such as
   *     retry policy and timeout settings for the operation.
   * @param opContext An {@link OperationContext} object for tracking the current operation.
   * @return A {@link TableResult} containing the results of executing the operation.
   * @throws StorageException if an error occurs in the storage operation.
   */
  private TableResult performDelete(
      final CloudTableClient client,
      final String tableName,
      final TableRequestOptions options,
      final OperationContext opContext)
      throws StorageException {
    final boolean isTableEntry = TableConstants.TABLES_SERVICE_TABLES_NAME.equals(tableName);
    final String tableIdentity =
        isTableEntry
            ? this.getEntity()
                .writeEntity(opContext)
                .get(TableConstants.TABLE_NAME)
                .getValueAsString()
            : null;

    if (!isTableEntry) {
      Utility.assertNotNullOrEmpty("Delete requires a valid ETag", this.getEntity().getEtag());
      Utility.assertNotNullOrEmpty(
          "Delete requires a valid PartitionKey", this.getEntity().getPartitionKey());
      Utility.assertNotNullOrEmpty("Delete requires a valid RowKey", this.getEntity().getRowKey());
    }

    final StorageOperation<CloudTableClient, TableOperation, TableResult> impl =
        new StorageOperation<CloudTableClient, TableOperation, TableResult>(options) {
          @Override
          public TableResult execute(
              final CloudTableClient client,
              final TableOperation operation,
              final OperationContext opContext)
              throws Exception {

            final HttpURLConnection request =
                TableRequest.delete(
                    client.getTransformedEndPoint(opContext),
                    tableName,
                    generateRequestIdentity(isTableEntry, tableIdentity, false),
                    operation.getEntity().getEtag(),
                    options.getTimeoutIntervalInMs(),
                    null,
                    options,
                    opContext);

            client.getCredentials().signRequestLite(request, -1L, opContext);

            this.setResult(ExecutionEngine.processRequest(request, opContext));

            if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND
                || this.getResult().getStatusCode() == HttpURLConnection.HTTP_CONFLICT) {
              throw TableServiceException.generateTableServiceException(
                  false, this.getResult(), operation, request.getErrorStream());
            }

            if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_NO_CONTENT) {
              throw TableServiceException.generateTableServiceException(
                  true, this.getResult(), operation, request.getErrorStream());
            }

            return operation.parseResponse(null, this.getResult().getStatusCode(), null, opContext);
          }
        };

    return ExecutionEngine.executeWithRetry(
        client, this, impl, options.getRetryPolicyFactory(), opContext);
  }