/**
 * Copyright Microsoft Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.microsoft.azure.storage.queue;

import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Stack;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.Utility;

/**
 * RESERVED FOR INTERNAL USE. A class used to deserialize a list of queue messages.
 */
final class QueueMessageHandler extends DefaultHandler {

    private final Stack<String> elementStack = new Stack<String>();
    private StringBuilder bld = new StringBuilder();

    private final ArrayList<CloudQueueMessage> messages = new ArrayList<CloudQueueMessage>();

    private final boolean shouldEncodeMessage;

    private CloudQueueMessage message = new CloudQueueMessage();

    private QueueMessageHandler(final boolean shouldEncodeMessage) {
        this.shouldEncodeMessage = shouldEncodeMessage;
    }

    /**
     * Populates CloudQueueMessage objects from the XMLStreamReader; the reader must be at the Start element of
     * QueuesElement.
     *
     * @param stream
     *            The InputStream object to deserialize from.
     * @param shouldEncodeMessage
     *            A flag indicating whether messages should be base-64 encoded.
     *
     * @return An ArrayList of {@link CloudQueueMessage} from the
     *         stream.
     *
     * @throws IOException
     * @throws SAXException
     * @throws ParserConfigurationException
     */
    public static ArrayList<CloudQueueMessage> readMessages(final InputStream stream, final boolean shouldEncodeMessage)
            throws SAXException, IOException, ParserConfigurationException {
        SAXParser saxParser = Utility.getSAXParser();
        QueueMessageHandler handler = new QueueMessageHandler(shouldEncodeMessage);
        saxParser.parse(stream, handler);

        return handler.messages;
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        this.elementStack.push(localName);

        if (QueueConstants.QUEUE_MESSAGE_ELEMENT.equals(localName)) {
            this.message = new CloudQueueMessage();
            this.message.setMessageType(this.shouldEncodeMessage ? QueueMessageType.BASE_64_ENCODED
                    : QueueMessageType.RAW_STRING);
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        String currentNode = this.elementStack.pop();

        // if the node popped from the stack and the localName don't match, the xml document is improperly formatted
        if (!localName.equals(currentNode)) {
            throw new SAXException(SR.INVALID_RESPONSE_RECEIVED);
        }

        String value = this.bld.toString();
        if (value.isEmpty()) {
            value = null;
        }

        if (QueueConstants.QUEUE_MESSAGE_ELEMENT.equals(localName)) {
            this.messages.add(this.message);
        }
        else if (QueueConstants.MESSAGE_ID_ELEMENT.equals(currentNode)) {
            this.message.setMessageId(value);
        }
        else if (QueueConstants.INSERTION_TIME_ELEMENT.equals(currentNode)) {
            try {
                this.message.setInsertionTime(Utility.parseRFC1123DateFromStringInGMT(value));
            }
            catch (ParseException e) {
                throw new SAXException(e);
            }
        }
        else if (QueueConstants.EXPIRATION_TIME_ELEMENT.equals(currentNode)) {
            try {
                this.message.setExpirationTime(Utility.parseRFC1123DateFromStringInGMT(value));
            }
            catch (ParseException e) {
                throw new SAXException(e);
            }
        }
        else if (QueueConstants.POP_RECEIPT_ELEMENT.equals(currentNode)) {
            this.message.setPopReceipt(value);
        }
        else if (QueueConstants.TIME_NEXT_VISIBLE_ELEMENT.equals(currentNode)) {
            try {
                this.message.setNextVisibleTime(Utility.parseRFC1123DateFromStringInGMT(value));
            }
            catch (ParseException e) {
                throw new SAXException(e);
            }
        }
        else if (QueueConstants.DEQUEUE_COUNT_ELEMENT.equals(currentNode)) {
            this.message.setDequeueCount(Integer.parseInt(value));
        }
        else if (QueueConstants.MESSAGE_TEXT_ELEMENT.equals(currentNode)) {
            this.message.messageContent = value;
        }

        this.bld = new StringBuilder();
    }

    @Override
    public void characters(char ch[], int start, int length) throws SAXException {
        this.bld.append(ch, start, length);
    }
}


/**
 * Copyright Microsoft Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.microsoft.azure.storage.table;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;

import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.DoesServiceRequest;
import com.microsoft.azure.storage.IPRange;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.ResultContinuation;
import com.microsoft.azure.storage.ResultSegment;
import com.microsoft.azure.storage.SharedAccessPolicyHandler;
import com.microsoft.azure.storage.SharedAccessPolicySerializer;
import com.microsoft.azure.storage.SharedAccessProtocols;
import com.microsoft.azure.storage.StorageCredentials;
import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature;
import com.microsoft.azure.storage.StorageErrorCodeStrings;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.StorageExtendedErrorInformation;
import com.microsoft.azure.storage.StorageUri;
import com.microsoft.azure.storage.core.ExecutionEngine;
import com.microsoft.azure.storage.core.PathUtility;
import com.microsoft.azure.storage.core.RequestLocationMode;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.SharedAccessSignatureHelper;
import com.microsoft.azure.storage.core.StorageCredentialsHelper;
import com.microsoft.azure.storage.core.StorageRequest;
import com.microsoft.azure.storage.core.UriQueryBuilder;
import com.microsoft.azure.storage.core.Utility;

/**
 * Represents a table in the Microsoft Azure Table service.
 */
public final class CloudTable {
    /**
     * The name of the table.
     */
    private String name;

    /**
     * Holds the list of URIs for all locations.
     */
    private StorageUri storageUri;

    /**
     * A reference to the associated service client.
     */
    private CloudTableClient tableServiceClient;

    /**
     * Gets the name of the table.
     *
     * @return A String object that represents the name of the table.
     */
    public String getName() {
        return this.name;
    }

    /**
     * Gets the table service client associated with this queue.
     *
     * @return A {@link CloudTableClient} object that represents the service client associated with this table.
     */
    public CloudTableClient getServiceClient() {
        return this.tableServiceClient;
    }

    /**
     * Returns the list of URIs for all locations.
     *
     * @return A {@link StorageUri} that represents the list of URIs for all locations..
     */
    public final StorageUri getStorageUri() {
        return this.storageUri;
    }

    /**
     * Gets the absolute URI for this table.
     *
     * @return A java.net.URI object that represents the URI for this table.
     */
    public URI getUri() {
        return this.storageUri.getPrimaryUri();
    }

    /**
     * Creates an instance of the CloudTable class using the specified table URI. The table URI must
     * include a SAS token.
     *
     * @param uri
     *            A java.net.URI object that represents the absolute URI of the table.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudTable(final URI uri) throws StorageException {
        this(new StorageUri(uri, null));
    }

    /**
     * Creates an instance of the CloudTable class using the specified table URI. The table URI must
     * include a SAS token.
     *
     * @param uri
     *            A {@link StorageUri} object that represents the absolute URI of the table.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudTable(final StorageUri uri) throws StorageException {
        this(uri, (StorageCredentials)null);
    }

    /**
     * Creates an instance of the CloudTable class using the specified table URI and credentials.
     *
     * @param uri
     *            A java.net.URI object that represents the absolute URI of the table.
     * @param credentials
     *            A {@link StorageCredentials} object used to authenticate access.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudTable(final URI uri, final StorageCredentials credentials) throws StorageException {
        this(new StorageUri(uri, null), credentials);
    }

    /**
     * Creates an instance of the CloudTable class using the specified table StorageUri and credentials.
     *
     * @param uri
     *            A {@link StorageUri} object that represents the absolute StorageUri of the table.
     * @param credentials
     *            A {@link StorageCredentials} object used to authenticate access.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudTable(final StorageUri uri, final StorageCredentials credentials) throws StorageException {
        this.parseQueryAndVerify(uri, credentials);
    }

    /**
     * Creates an instance of the CloudTable class using the specified name and client.
     *
     * @param tableName
     *            A String which represents the name of the table, which must adhere to table naming rules.
     *            The table name should not include any path separator characters (/).
     *            Table names are case insensitive, must be unique within an account and must be between 3-63 characters
     *            long. Table names must start with an cannot begin with a numeric character and may only contain
     *            alphanumeric characters. Some table names are reserved, including "table".
     * @param client
     *            A {@link CloudTableClient} object that represents the associated service client, and that specifies
     *            the endpoint for the Table service.
     *
     * @throws URISyntaxException
     *             If the resource URI constructed based on the tableName is invalid.
     * @throws StorageException
     *             If a storage service error occurred.
     * @see Understanding the Table Service Data
     *      Model
     */
    protected CloudTable(final String tableName, final CloudTableClient client) throws URISyntaxException,
    StorageException {
        Utility.assertNotNull("client", client);
        Utility.assertNotNull("tableName", tableName);

        this.storageUri = PathUtility.appendPathToUri(client.getStorageUri(), tableName);
        this.name = tableName;
        this.tableServiceClient = client;
    }

    /**
     * Creates the table in the storage service with default request options.
     *

     * This method invokes the Create Table
     * REST API to create the specified table, using the Table service endpoint and storage account credentials of this
     * instance.
     *
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public void create() throws StorageException {
        this.create(null /* options */, null /* opContext */);
    }

    /**
     * Creates the table in the storage service, using the specified {@link TableRequestOptions} and
     * {@link OperationContext}.
     *

     * This method invokes the Create Table
     * REST API to create the specified table, using the Table service endpoint and storage account credentials of this
     * instance.
     *
     * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the
     * operation.
     *
     * @param options
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     *
     * @throws StorageException
     *             If an error occurs accessing the storage service, or because the table cannot be
     *             created, or already exists.
     */
    @DoesServiceRequest
    public void create(TableRequestOptions options, OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient);

        Utility.assertNotNullOrEmpty("tableName", this.name);

        final DynamicTableEntity tableEntry = new DynamicTableEntity();
        tableEntry.getProperties().put(TableConstants.TABLE_NAME, new EntityProperty(this.name));

        TableOperation.insert(tableEntry).execute(this.tableServiceClient, TableConstants.TABLES_SERVICE_TABLES_NAME,
                options, opContext);
    }

    /**
     * Creates the table in the storage service using default request options if it does not already exist.
     *
     * @return true if the table is created in the storage service; otherwise false.
     *
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public boolean createIfNotExists() throws StorageException {
        return this.createIfNotExists(null /* options */, null /* opContext */);
    }

    /**
     * Creates the table in the storage service with the specified request options and operation context, if it does not
     * already exist.
     *
     * @param options
     *            A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudTableClient}).
     * @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 true if the table did not already exist and was created; otherwise false .
     *
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public boolean createIfNotExists(TableRequestOptions options, OperationContext opContext) throws StorageException {
        options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient);

        boolean exists = this.exists(true, options, opContext);
        if (exists) {
            return false;
        }
        else {
            try {
                this.create(options, opContext);
                return true;
            }
            catch (StorageException e) {
                if (e.getHttpStatusCode() == HttpURLConnection.HTTP_CONFLICT
                        && StorageErrorCodeStrings.TABLE_ALREADY_EXISTS.equals(e.getErrorCode())) {
                    return false;
                }
                else {
                    throw e;
                }
            }
        }
    }

    /**
     * Deletes the table from the storage service.
     *
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public void delete() throws StorageException {
        this.delete(null /* options */, null /* opContext */);
    }

    /**
     * Deletes the table from the storage service, using the specified request options and operation context.
     *
     * @param options
     *            A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudTableClient}).
     * @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.
     *
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public void delete(TableRequestOptions options, OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient);

        Utility.assertNotNullOrEmpty("tableName", this.name);
        final DynamicTableEntity tableEntry = new DynamicTableEntity();
        tableEntry.getProperties().put(TableConstants.TABLE_NAME, new EntityProperty(this.name));

        TableOperation deleteOp = new TableOperation(tableEntry, TableOperationType.DELETE);
        deleteOp.execute(this.tableServiceClient, TableConstants.TABLES_SERVICE_TABLES_NAME, options, opContext);
    }

    /**
     * Deletes the table from the storage service, if it exists.
     *
     * @return true if the table existed in the storage service and has been deleted; otherwise
     *         false.
     *
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public boolean deleteIfExists() throws StorageException {
        return this.deleteIfExists(null /* options */, null /* opContext */);
    }

    /**
     * Deletes the table from the storage service using the specified request options and operation context, if it
     * exists.
     *
     * @param options
     *            A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudTableClient}).
     * @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 value of true if the table existed in the storage service and has been deleted, otherwise
     *         false.
     *
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public boolean deleteIfExists(TableRequestOptions options, OperationContext opContext) throws StorageException {
        options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient);

        if (this.exists(true, options, opContext)) {
            try {
                this.delete(options, opContext);
            }
            catch (StorageException ex) {
                if (ex.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND
                        && StorageErrorCodeStrings.RESOURCE_NOT_FOUND.equals(ex.getErrorCode())) {
                    return false;
                }
                else {
                    throw ex;
                }
            }
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * Executes the specified batch operation on a table as an atomic operation. A batch operation may contain up to 100
     * individual table operations, with the requirement that each operation entity must have same partition key. Only
     * one retrieve operation is allowed per batch. Note that the total payload of a batch operation is limited to 4MB.
     *

     * This method invokes an Entity Group
     * Transaction on the REST API to execute the specified batch operation on the table as an atomic unit, using
     * the Table service endpoint and storage account credentials of this instance.
     *
     * @param batch
     *            The {@link TableBatchOperation} object representing the operations to execute on the table.
     *
     * @return
     *         A java.util.ArrayList of {@link TableResult} that contains the results, in order, of
     *         each {@link TableOperation} in the {@link TableBatchOperation} on the named table.
     *
     * @throws StorageException
     *             if an error occurs accessing the storage service, or the operation fails.
     */
    @DoesServiceRequest
    public ArrayList<TableResult> execute(final TableBatchOperation batch) throws StorageException {
        return this.execute(batch, null /* options */, null /* opContext */);
    }

    /**
     * Executes the specified batch operation on a table as an atomic operation, using the specified
     * {@link TableRequestOptions} and {@link OperationContext}. A batch operation may contain up to 100 individual
     * table operations, with the requirement that each operation entity must have same partition key. Only one retrieve
     * operation is allowed per batch. Note that the total payload of a batch operation is limited to 4MB.
     *

     * This method invokes an Entity Group
     * Transaction on the REST API to execute the specified batch operation on the table as an atomic unit, using
     * the Table service endpoint and storage account credentials of this instance.
     *
     * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the
     * operation.
     *
     * @param batch
     *            The {@link TableBatchOperation} object representing the operations to execute on the table.
     * @param options
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     *
     * @return
     *         A java.util.ArrayList of {@link TableResult} that contains the results, in order, of
     *         each {@link TableOperation} in the {@link TableBatchOperation} on the named table.
     *
     * @throws StorageException
     *             if an error occurs accessing the storage service, or the operation fails.
     */
    @DoesServiceRequest
    public ArrayList<TableResult> execute(final TableBatchOperation batch, TableRequestOptions options,
            OperationContext opContext) throws StorageException {
        Utility.assertNotNull("batch", batch);
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = TableRequestOptions.populateAndApplyDefaults(options, this.getServiceClient());
        return batch.execute(this.getServiceClient(), this.getName(), options, opContext);
    }

    /**
     * Executes the operation on a table.
     *

     * This method will invoke the Table Service
     * REST API to execute the specified operation on the table, using the Table service endpoint and storage
     * account credentials of this instance.
     *
     * @param operation
     *            The {@link TableOperation} object representing the operation to execute on the table.
     *
     * @return
     *         A {@link TableResult} containing the result of executing the {@link TableOperation} on the table.
     *
     * @throws StorageException
     *             if an error occurs accessing the storage service, or the operation fails.
     */
    @DoesServiceRequest
    public TableResult execute(final TableOperation operation) throws StorageException {
        return this.execute(operation, null /* options */, null /* opContext */);
    }

    /**
     * Executes the operation on a table, using the specified {@link TableRequestOptions} and {@link OperationContext}.
     *

     * This method will invoke the Table Service
     * REST API to execute the specified operation on the table, using the Table service endpoint and storage
     * account credentials of this instance.
     *
     * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the
     * operation.
     *
     * @param operation
     *            The {@link TableOperation} object representing the operation to execute on the table.
     * @param options
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     *
     * @return
     *         A {@link TableResult} containing the result of executing the {@link TableOperation} on the table.
     *
     * @throws StorageException
     *             if an error occurs accessing the storage service, or the operation fails.
     */
    @DoesServiceRequest
    public TableResult execute(final TableOperation operation, final TableRequestOptions options,
            final OperationContext opContext) throws StorageException {
        Utility.assertNotNull("operation", operation);
        return operation.execute(this.getServiceClient(), this.getName(), options, opContext);
    }

    /**
     * Executes a query, applying the specified {@link EntityResolver} to the result.
     *

     * This method will invoke a Query
     * Entities operation on the Table Service
     * REST API to query the table, using the Table service endpoint and storage account credentials of this
     * instance.
     *
     * @param query
     *            A {@link TableQuery} instance specifying the table to query and the query parameters to use.
     * @param resolver
     *            An {@link EntityResolver} instance which creates a projection of the table query result entities into
     *            the specified type R.
     *
     * @return
     *         A collection implementing the Iterable interface containing the projection into type
     *         R of the results of executing the query.
     */
    @DoesServiceRequest
    public <R> Iterable<R> execute(final TableQuery<?> query, final EntityResolver<R> resolver) {
        return this.execute(query, resolver, null /* options */, null /* opContext */);
    }

    /**
     * Executes a query, applying the specified {@link EntityResolver} to the result, using the
     * specified {@link TableRequestOptions} and {@link OperationContext}.
     *

     * This method will invoke a Query
     * Entities operation on the Table Service
     * REST API to query the table, using the Table service endpoint and storage account credentials of this
     * instance.
     *
     * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the
     * operation.
     *
     * @param query
     *            A {@link TableQuery} instance specifying the table to query and the query parameters to use.
     * @param resolver
     *            An {@link EntityResolver} instance which creates a projection of the table query result entities into
     *            the specified type R.
     * @param options
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     *
     * @return
     *         A collection implementing the Iterable interface containing the projection into type
     *         R of the results of executing the query.
     */
    @DoesServiceRequest
    @SuppressWarnings({ "unchecked" })
    public <R> Iterable<R> execute(final TableQuery<?> query, final EntityResolver<R> resolver,
            final TableRequestOptions options, final OperationContext opContext) {
        Utility.assertNotNull("query", query);
        Utility.assertNotNull(SR.QUERY_REQUIRES_VALID_CLASSTYPE_OR_RESOLVER, resolver);
        query.setSourceTableName(this.getName());
        return (Iterable<R>) this.getServiceClient().generateIteratorForQuery(query, resolver, options, opContext);
    }

    /**
     * Executes a query.
     *

     * This method will invoke a Query
     * Entities operation on the Table Service
     * REST API to query the table, using the Table service endpoint and storage account credentials of this
     * instance.
     *
     * @param query
     *            A {@link TableQuery} instance specifying the table to query and the query parameters to use,
     *            specialized for a type T implementing {@link TableEntity}.
     *
     * @return
     *         A collection implementing the Iterable interface specialized for type T of the results of
     *         executing the query.
     */
    @DoesServiceRequest
    public <T extends TableEntity> Iterable<T> execute(final TableQuery<T> query) {
        return this.execute(query, null /* options */, null /* opContext */);
    }

    /**
     * Executes a query, using the specified {@link TableRequestOptions} and {@link OperationContext}.
     *

     * This method will invoke a Query
     * Entities operation on the Table Service
     * REST API to query the table, using the Table service endpoint and storage account credentials of this
     * instance.
     *
     * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the
     * operation.
     *
     * @param query
     *            A {@link TableQuery} instance specifying the table to query and the query parameters to use,
     *            specialized for a type T implementing {@link TableEntity}.
     * @param options
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     *
     * @return
     *         A collection implementing the Iterable interface specialized for type T of the results of
     *         executing the query.
     */
    @SuppressWarnings({ "unchecked" })
    @DoesServiceRequest
    public <T extends TableEntity> Iterable<T> execute(final TableQuery<T> query, final TableRequestOptions options,
            final OperationContext opContext) {
        Utility.assertNotNull("query", query);
        Utility.assertNotNull(SR.QUERY_REQUIRES_VALID_CLASSTYPE_OR_RESOLVER, query.getClazzType());
        query.setSourceTableName(this.getName());
        return (Iterable<T>) this.getServiceClient().generateIteratorForQuery(query, null, options, opContext);
    }

    /**
     * Executes a query in segmented mode with the specified {@link ResultContinuation} continuation token,
     * applying the {@link EntityResolver} to the result.
     * Executing a query with executeSegmented allows the query to be resumed after returning partial
     * results, using information returned by the server in the {@link ResultSegment} object.
     *

     * This method will invoke a Query
     * Entities operation on the Table Service
     * REST API to query the table, using the Table service endpoint and storage account credentials of this
     * instance.
     *
     * @param query
     *            A {@link TableQuery} instance specifying the table to query and the query parameters to use.
     * @param resolver
     *            An {@link EntityResolver} instance which creates a projection of the table query result entities into
     *            the specified type R.
     * @param continuationToken
     *            A {@link ResultContinuation} object representing a continuation token from the server when the
     *            operation returns a partial result. Specify null on the initial call. Call the
     *            {@link ResultSegment#getContinuationToken()} method on the result to obtain the
     *            {@link ResultContinuation} object to use in the next call to resume the query.
     *
     * @return
     *         A {@link ResultSegment} containing the projection into type R of the results of executing
     *         the query.
     *
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public <R> ResultSegment<R> executeSegmented(final TableQuery<?> query, final EntityResolver<R> resolver,
            final ResultContinuation continuationToken) throws StorageException {
        return this.executeSegmented(query, resolver, continuationToken, null /* options */, null /* opContext */);
    }

    /**
     * Executes a query in segmented mode with the specified {@link ResultContinuation} continuation token,
     * using the specified {@link TableRequestOptions} and {@link OperationContext}, applying the {@link EntityResolver}
     * to the result.
     * Executing a query with executeSegmented allows the query to be resumed after returning partial
     * results, using information returned by the server in the {@link ResultSegment} object.
     *

     * This method will invoke a Query
     * Entities operation on the Table Service
     * REST API to query the table, using the Table service endpoint and storage account credentials of this
     * instance.
     *
     * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the
     * operation.
     *
     * @param query
     *            A {@link TableQuery} instance specifying the table to query and the query parameters to use.
     * @param resolver
     *            An {@link EntityResolver} instance which creates a projection of the table query result entities into
     *            the specified type R.
     * @param continuationToken
     *            A {@link ResultContinuation} object representing a continuation token from the server when the
     *            operation returns a partial result. Specify null on the initial call. Call the
     *            {@link ResultSegment#getContinuationToken()} method on the result to obtain the
     *            {@link ResultContinuation} object to use in the next call to resume the query.
     * @param options
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     *
     * @return
     *         A {@link ResultSegment} containing the projection into type R of the results of executing
     *         the query.
     *
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    @SuppressWarnings({ "unchecked" })
    public <R> ResultSegment<R> executeSegmented(final TableQuery<?> query, final EntityResolver<R> resolver,
            final ResultContinuation continuationToken, final TableRequestOptions options,
            final OperationContext opContext) throws StorageException {
        Utility.assertNotNull(SR.QUERY_REQUIRES_VALID_CLASSTYPE_OR_RESOLVER, resolver);
        query.setSourceTableName(this.getName());
        return (ResultSegment<R>) this.getServiceClient().executeQuerySegmentedImpl(query, resolver, continuationToken,
                options, opContext);
    }

    /**
     * Executes a query in segmented mode with a {@link ResultContinuation} continuation token.
     * Executing a query with executeSegmented allows the query to be resumed after returning partial
     * results, using information returned by the server in the {@link ResultSegment} object.
     *

     * This method will invoke a Query
     * Entities operation on the Table Service
     * REST API to query the table, using the Table service endpoint and storage account credentials of this
     * instance.
     *
     * @param query
     *            A {@link TableQuery} instance specifying the table to query and the query parameters to use,
     *            specialized for a type T implementing {@link TableEntity}.
     * @param continuationToken
     *            A {@link ResultContinuation} object representing a continuation token from the server when the
     *            operation returns a partial result. Specify null on the initial call. Call the
     *            {@link ResultSegment#getContinuationToken()} method on the result to obtain the
     *            {@link ResultContinuation} object to use in the next call to resume the query.
     *
     * @return
     *         A {@link ResultSegment} specialized for type T of the results of executing the query.
     *
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public <T extends TableEntity> ResultSegment<T> executeSegmented(final TableQuery<T> query,
            final ResultContinuation continuationToken) throws StorageException {
        return this.executeSegmented(query, continuationToken, null /* options */, null /* opContext */);
    }

    /**
     * Executes a query in segmented mode with a {@link ResultContinuation} continuation token,
     * using the specified {@link TableRequestOptions} and {@link OperationContext}.
     * Executing a query with executeSegmented allows the query to be resumed after returning partial
     * results, using information returned by the server in the {@link ResultSegment} object.
     *

     * This method will invoke a Query
     * Entities operation on the Table Service
     * REST API to query the table, using the Table service endpoint and storage account credentials of this
     * instance.
     *
     * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the
     * operation.
     *
     * @param query
     *            A {@link TableQuery} instance specifying the table to query and the query parameters to use,
     *            specialized for a type T implementing {@link TableEntity}.
     * @param continuationToken
     *            A {@link ResultContinuation} object representing a continuation token from the server when the
     *            operation returns a partial result. Specify null on the initial call. Call the
     *            {@link ResultSegment#getContinuationToken()} method on the result to obtain the
     *            {@link ResultContinuation} object to use in the next call to resume the query.
     * @param options
     *            A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout
     *            settings for the operation. Specify null to use the request options specified on the
     *            {@link CloudTableClient}.
     * @param opContext
     *            An {@link OperationContext} object for tracking the current operation. Specify null to
     *            safely ignore operation context.
     *
     * @return
     *         A {@link ResultSegment} specialized for type T of the results of executing the query.
     *
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    @SuppressWarnings({ "unchecked" })
    public <T extends TableEntity> ResultSegment<T> executeSegmented(final TableQuery<T> query,
            final ResultContinuation continuationToken, final TableRequestOptions options,
            final OperationContext opContext) throws StorageException {
        Utility.assertNotNull("query", query);
        query.setSourceTableName(this.getName());
        return (ResultSegment<T>) this.getServiceClient().executeQuerySegmentedImpl(query, null, continuationToken,
                options, opContext);
    }

    /**
     * Returns a value that indicates whether the table exists in the storage service.
     *
     * @return true if the table exists in the storage service; otherwise false.
     *
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public boolean exists() throws StorageException {
        return this.exists(null /* options */, null /* opContext */);
    }

    /**
     * Returns a value that indicates whether the table exists in the storage service, using the specified request
     * options and operation context.
     *
     * @param options
     *            A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudTableClient}).
     * @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 true if the table exists in the storage service, otherwise false.
     *
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public boolean exists(TableRequestOptions options, OperationContext opContext) throws StorageException {
        return this.exists(false, options, opContext);
    }

    /**
     * Returns a value that indicates whether the table exists in the storage service, using the specified request
     * options and operation context.
     *
     * @param options
     *            A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudTableClient}).
     * @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 true if the table exists in the storage service, otherwise false.
     *
     * @throws StorageException
     *             If a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    private boolean exists(final boolean primaryOnly, TableRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient);

        Utility.assertNotNullOrEmpty("tableName", this.name);

        QueryTableOperation operation = (QueryTableOperation) TableOperation.retrieve(this.name /* Used As PK */,
                null/* Row Key */, DynamicTableEntity.class);
        operation.setPrimaryOnlyRetrieve(primaryOnly);

        final TableResult result = operation.execute(this.tableServiceClient,
                TableConstants.TABLES_SERVICE_TABLES_NAME, options, opContext);

        if (result.getHttpStatusCode() == HttpURLConnection.HTTP_OK) {
            return true;
        }
        else if (result.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
            return false;
        }
        else {
            throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, SR.UNEXPECTED_STATUS_CODE_RECEIVED,
                    result.getHttpStatusCode(), null /* extendedErrorInfo */, null /* innerException */);
        }
    }

    /**
     * Uploads the table's permissions.
     *
     * @param permissions
     *            A {@link TablePermissions} object that represents the permissions to upload.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadPermissions(final TablePermissions permissions) throws StorageException {
        this.uploadPermissions(permissions, null /* options */, null /* opContext */);
    }

    /**
     * Uploads the table's permissions using the specified request options and operation context.
     *
     * @param permissions
     *            A {@link TablePermissions} object that represents the permissions to upload.
     * @param options
     *            A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudTableClient}).
     * @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.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadPermissions(final TablePermissions permissions, TableRequestOptions options,
            OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient);

        ExecutionEngine.executeWithRetry(this.tableServiceClient, this,
                this.uploadPermissionsImpl(permissions, options), options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudTableClient, CloudTable, Void> uploadPermissionsImpl(
            final TablePermissions permissions, final TableRequestOptions options) throws StorageException {
        final StringWriter outBuffer = new StringWriter();

        try {
            SharedAccessPolicySerializer.writeSharedAccessIdentifiersToStream(permissions.getSharedAccessPolicies(),
                    outBuffer);
            final byte[] aclBytes = outBuffer.toString().getBytes(Constants.UTF8_CHARSET);

            final StorageRequest<CloudTableClient, CloudTable, Void> putRequest = new StorageRequest<CloudTableClient, CloudTable, Void>(
                    options, this.getStorageUri()) {

                @Override
                public HttpURLConnection buildRequest(CloudTableClient client, CloudTable table,
                        OperationContext context) throws Exception {
                    this.setSendStream(new ByteArrayInputStream(aclBytes));
                    this.setLength((long) aclBytes.length);
                    return TableRequest.setAcl(table.getStorageUri().getUri(this.getCurrentLocation()), options,
                            context);
                }

                @Override
                public void signRequest(HttpURLConnection connection, CloudTableClient client, OperationContext context)
                        throws Exception {
                    StorageRequest.signTableRequest(connection, client, aclBytes.length, context);
                }

                @Override
                public Void preProcessResponse(CloudTable parentObject, CloudTableClient client,
                        OperationContext context) throws Exception {
                    if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_NO_CONTENT) {
                        this.setNonExceptionedRetryableFailure(true);
                    }

                    return null;
                }

                @Override
                public StorageExtendedErrorInformation parseErrorDetails() {
                    return TableStorageErrorDeserializer.parseErrorDetails(this);
                }
            };

            return putRequest;
        }
        catch (IllegalArgumentException e) {
            // The request was not even made. There was an error while trying to write the message. Just throw.
            StorageException translatedException = StorageException.translateClientException(e);
            throw translatedException;
        }
        catch (IllegalStateException e) {
            // The request was not even made. There was an error while trying to write the message. Just throw.
            StorageException translatedException = StorageException.translateClientException(e);
            throw translatedException;
        }
        catch (IOException e) {
            // The request was not even made. There was an error while trying to write the message. Just throw.
            StorageException translatedException = StorageException.translateClientException(e);
            throw translatedException;
        }
    }

    /**
     * Downloads the permission settings for the table.
     *
     * @return A {@link TablePermissions} object that represents the container's permissions.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public TablePermissions downloadPermissions() throws StorageException {
        return this.downloadPermissions(null /* options */, null /* opContext */);
    }

    /**
     * Downloads the permissions settings for the table using the specified request options and operation context.
     *
     * @param options
     *            A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudTableClient}).
     * @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 TablePermissions} object that represents the table's permissions.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public TablePermissions downloadPermissions(TableRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = TableRequestOptions.populateAndApplyDefaults(options, this.tableServiceClient);

        return ExecutionEngine.executeWithRetry(this.tableServiceClient, this, this.downloadPermissionsImpl(options),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudTableClient, CloudTable, TablePermissions> downloadPermissionsImpl(
            final TableRequestOptions options) {
        final StorageRequest<CloudTableClient, CloudTable, TablePermissions> getRequest = new StorageRequest<CloudTableClient, CloudTable, TablePermissions>(
                options, this.getStorageUri()) {

            @Override
            public void setRequestLocationMode() {
                this.setRequestLocationMode(RequestLocationMode.PRIMARY_OR_SECONDARY);
            }

            @Override
            public HttpURLConnection buildRequest(CloudTableClient client, CloudTable table, OperationContext context)
                    throws Exception {
                return TableRequest.getAcl(table.getStorageUri().getUri(this.getCurrentLocation()), options, context);
            }

            @Override
            public void signRequest(HttpURLConnection connection, CloudTableClient client, OperationContext context)
                    throws Exception {
                StorageRequest.signTableRequest(connection, client, -1L, context);
            }

            @Override
            public TablePermissions preProcessResponse(CloudTable parentObject, CloudTableClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                }

                return new TablePermissions();
            }

            @Override
            public TablePermissions postProcessResponse(HttpURLConnection connection, CloudTable table,
                    CloudTableClient client, OperationContext context, TablePermissions permissions) throws Exception {
                HashMap<String, SharedAccessTablePolicy> accessIds = SharedAccessPolicyHandler.getAccessIdentifiers(
                        this.getConnection().getInputStream(), SharedAccessTablePolicy.class);
                for (final String key : accessIds.keySet()) {
                    permissions.getSharedAccessPolicies().put(key, accessIds.get(key));
                }

                return permissions;
            }

            @Override
            public StorageExtendedErrorInformation parseErrorDetails() {
                return TableStorageErrorDeserializer.parseErrorDetails(this);
            }
        };

        return getRequest;
    }

    /**
     * Creates a shared access signature for the table.
     *
     * @param policy
     *            A {@link SharedAccessTablePolicy} object which represents the access policy for the shared access
     *            signature.
     * @param accessPolicyIdentifier
     *            A String which represents a table-level access policy.
     * @param startPartitionKey
     *            A String which represents the starting partition key.
     * @param startRowKey
     *            A String which represents the starting row key.
     * @param endPartitionKey
     *            A String which represents the ending partition key.
     * @param endRowKey
     *            A String which represents the ending end key.
     *
     * @return A String containing the shared access signature for the table.
     *
     * @throws InvalidKeyException
     *             If an invalid key was passed.
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws IllegalArgumentException
     *             If an unexpected value is passed.
     */
    public String generateSharedAccessSignature(final SharedAccessTablePolicy policy,
            final String accessPolicyIdentifier, final String startPartitionKey, final String startRowKey,
            final String endPartitionKey, final String endRowKey) throws InvalidKeyException, StorageException {

        return generateSharedAccessSignature(policy, accessPolicyIdentifier,
                startPartitionKey, startRowKey, endPartitionKey, endRowKey, null /* IP Range */, null /* Protocols */);
    }

    /**
     * Creates a shared access signature for the table.
     *
     * @param policy
     *            A {@link SharedAccessTablePolicy} object which represents the access policy for the shared access
     *            signature.
     * @param accessPolicyIdentifier
     *            A String which represents a table-level access policy.
     * @param startPartitionKey
     *            A String which represents the starting partition key.
     * @param startRowKey
     *            A String which represents the starting row key.
     * @param endPartitionKey
     *            A String which represents the ending partition key.
     * @param endRowKey
     *            A String which represents the ending end key.
     * @param ipRange
     *            A {@link IPRange} object containing the range of allowed IP addresses.
     * @param protocols
     *            A {@link SharedAccessProtocols} representing the allowed Internet protocols.
     *
     * @return A String containing the shared access signature for the table.
     *
     * @throws InvalidKeyException
     *             If an invalid key was passed.
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws IllegalArgumentException
     *             If an unexpected value is passed.
     */
    public String generateSharedAccessSignature(
            final SharedAccessTablePolicy policy, final String accessPolicyIdentifier, final String startPartitionKey,
            final String startRowKey, final String endPartitionKey, final String endRowKey, final IPRange ipRange,
            final SharedAccessProtocols protocols)
            throws InvalidKeyException, StorageException {

        if (!StorageCredentialsHelper.canCredentialsSignRequest(this.tableServiceClient.getCredentials())) {
            throw new IllegalArgumentException(SR.CANNOT_CREATE_SAS_WITHOUT_ACCOUNT_KEY);
        }

        final String resourceName = this.getSharedAccessCanonicalName();

        final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForTable(
                policy, accessPolicyIdentifier, resourceName, ipRange, protocols,
                startPartitionKey, startRowKey, endPartitionKey, endRowKey, this.tableServiceClient);

        final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForTable(
                policy, startPartitionKey, startRowKey, endPartitionKey, endRowKey, accessPolicyIdentifier,
                ipRange, protocols, this.name,  signature);

        return builder.toString();
    }

    /**
     * Returns the canonical name for shared access.
     *
     * @return A String containing the canonical name for shared access.
     */
    private String getSharedAccessCanonicalName() {
        String accountName = this.getServiceClient().getCredentials().getAccountName();
        String tableNameLowerCase = this.getName().toLowerCase(Locale.ENGLISH);

        return String.format("/%s/%s/%s", SR.TABLE, accountName, tableNameLowerCase);
    }

    /**
     * Verifies the passed in URI. Then parses it and uses its components to populate this resource's properties.
     *
     * @param completeUri
     *            A {@link StorageUri} object which represents the complete URI.
     * @param credentials
     *            A {@link StorageCredentials} object used to authenticate access.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials)
            throws StorageException {
        Utility.assertNotNull("completeUri", completeUri);

        if (!completeUri.isAbsolute()) {
            throw new IllegalArgumentException(String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString()));
        }

        this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri);

        final StorageCredentialsSharedAccessSignature parsedCredentials =
                SharedAccessSignatureHelper.parseQuery(completeUri);

        if (credentials != null && parsedCredentials != null) {
            throw new IllegalArgumentException(SR.MULTIPLE_CREDENTIALS_PROVIDED);
        }

        try {
            final boolean usePathStyleUris = Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri());
            this.tableServiceClient = new CloudTableClient(PathUtility.getServiceClientBaseAddress(
                    this.getStorageUri(), usePathStyleUris), credentials != null ? credentials : parsedCredentials);
            this.name = PathUtility.getTableNameFromUri(storageUri.getPrimaryUri(), usePathStyleUris);
        }
        catch (final URISyntaxException e) {
            throw Utility.generateNewUnexpectedStorageException(e);
        }
    }
}


/**
 * Copyright Microsoft Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 *
 */
package com.microsoft.azure.storage;

import java.util.Date;

import com.microsoft.azure.storage.core.Utility;

/**
 * Represents the options to use while processing a given request.
 */
public abstract class RequestOptions {

    /**
     * The instance of the {@link RetryPolicyFactory} interface to use for the request.
     */
    private RetryPolicyFactory retryPolicyFactory;

    /**
     * The timeout interval, in milliseconds, to use for the request.
     */
    private Integer timeoutIntervalInMs;

    /**
     * The location mode of the request.
     */
    private LocationMode locationMode;

    /**
     * The maximum execution time, in milliseconds, across all potential retries.
     */
    private Integer maximumExecutionTimeInMs;

    /**
     * Represents the start time, in milliseconds, from the client's perspective.
     */
    private Long operationExpiryTime;

    /**
     * Creates an instance of the RequestOptions class.
     */
    public RequestOptions() {
        // Empty Default Ctor
    }

    /**
     * Creates an instance of the RequestOptions class by copying values from another
     * RequestOptions instance.
     *
     * @param other
     *            A RequestOptions object that represents the request options to copy.
     */
    public RequestOptions(final RequestOptions other) {
        if (other != null) {
            this.setRetryPolicyFactory(other.getRetryPolicyFactory());
            this.setTimeoutIntervalInMs(other.getTimeoutIntervalInMs());
            this.setLocationMode(other.getLocationMode());
            this.setMaximumExecutionTimeInMs(other.getMaximumExecutionTimeInMs());
            this.setOperationExpiryTimeInMs(other.getOperationExpiryTimeInMs());
        }
    }

    /**
     * Populates the default timeout, retry policy, and location mode from client if they are null.
     *
     * @param modifiedOptions
     *            The input options to copy from when applying defaults
     */
    protected static void applyBaseDefaultsInternal(final RequestOptions modifiedOptions) {
        Utility.assertNotNull("modifiedOptions", modifiedOptions);
        if (modifiedOptions.getRetryPolicyFactory() == null) {
            modifiedOptions.setRetryPolicyFactory(new RetryExponentialRetry());
        }

        if (modifiedOptions.getLocationMode() == null) {
            modifiedOptions.setLocationMode(LocationMode.PRIMARY_ONLY);
        }
    }

    /**
     * Populates any null fields in the first requestOptions object with values from the second requestOptions object.
     */
    protected static void populateRequestOptions(RequestOptions modifiedOptions,
            final RequestOptions clientOptions, final boolean setStartTime) {
        if (modifiedOptions.getRetryPolicyFactory() == null) {
            modifiedOptions.setRetryPolicyFactory(clientOptions.getRetryPolicyFactory());
        }

        if (modifiedOptions.getLocationMode() == null) {
            modifiedOptions.setLocationMode(clientOptions.getLocationMode());
        }

        if (modifiedOptions.getTimeoutIntervalInMs() == null) {
            modifiedOptions.setTimeoutIntervalInMs(clientOptions.getTimeoutIntervalInMs());
        }

        if (modifiedOptions.getMaximumExecutionTimeInMs() == null) {
            modifiedOptions.setMaximumExecutionTimeInMs(clientOptions.getMaximumExecutionTimeInMs());
        }

        if (modifiedOptions.getMaximumExecutionTimeInMs() != null
                && modifiedOptions.getOperationExpiryTimeInMs() == null && setStartTime) {
            modifiedOptions.setOperationExpiryTimeInMs(new Date().getTime()
                    + modifiedOptions.getMaximumExecutionTimeInMs());
        }
    }

    /**
     * Gets the retry policy to use for this request. For more information about the retry policy defaults, see
     * {@link #setRetryPolicyFactory(RetryPolicyFactory)}.
     *
     * @return An {@link RetryPolicyFactory} object that represents the current retry policy.
     *
     * @see RetryPolicy
     * @see RetryExponentialRetry
     * @see RetryLinearRetry
     * @see RetryNoRetry
     */
    public final RetryPolicyFactory getRetryPolicyFactory() {
        return this.retryPolicyFactory;
    }

    /**
     * Returns the timeout value for this request. For more information about the timeout defaults, see
     * {@link #setTimeoutIntervalInMs(Integer)}.
     *
     * @return The current timeout value, in milliseconds, for this request.
     */
    public final Integer getTimeoutIntervalInMs() {
        return this.timeoutIntervalInMs;
    }

    /**
     * Gets the default location mode for this request. For more information about location mode, see
     * {@link #setLocationMode(LocationMode)}.
     *
     * @return A {@link LocationMode} object that represents the location mode for this request.
     */
    public final LocationMode getLocationMode() {
        return this.locationMode;
    }

    /**
     * Gets the maximum execution time for this request. For more information about maximum execution time defaults, see
     * {@link #setMaximumExecutionTimeInMs(Integer)}.
     *
     * @return The current maximum execution time, in milliseconds, for this request.
     */
    public Integer getMaximumExecutionTimeInMs() {
        return this.maximumExecutionTimeInMs;
    }

    /**
     * RESERVED FOR INTERNAL USE.
     *
     * Returns the time at which this operation expires. This is computed by adding the time the operation begins and
     * the maximum execution time and will be null if maximum execution time is null. For more information about maximum
     * execution time, see {@link #setMaximumExecutionTimeInMs(Integer)}.
     *
     * @return The current operation expiry time, in milliseconds, for this request.
     */
    public Long getOperationExpiryTimeInMs() {
        return this.operationExpiryTime;
    }

    /**
     * Sets the RetryPolicyFactory object to use for this request.
     *

     * The default RetryPolicyFactory is set in the client and is by default {@link RetryExponentialRetry}. You can
     * change the RetryPolicyFactory on this request by setting this property. You can also change the value on the
     * {@link ServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the service
     * client will use that RetryPolicyFactory.
     *
     * @param retryPolicyFactory
     *            the RetryPolicyFactory object to use when making service requests.
     *
     * @see RetryPolicy
     * @see RetryExponentialRetry
     * @see RetryLinearRetry
     * @see RetryNoRetry
     */
    public final void setRetryPolicyFactory(final RetryPolicyFactory retryPolicyFactory) {
        this.retryPolicyFactory = retryPolicyFactory;
    }

    /**
     * Sets the timeout to use when making this request.
     *

     * The server timeout interval begins at the time that the complete request has been received by the service, and
     * the server begins processing the response. If the timeout interval elapses before the response is returned to the
     * client, the operation times out. The timeout interval resets with each retry, if the request is retried.
     *

     * The default server timeout is set in the client and is by default null, indicating no server timeout. You can
     * change the server timeout on this request by setting this property. You can also change the value on the
     * {@link ServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the service
     * client will use that server timeout.
     *
     * @param timeoutIntervalInMs
     *            The timeout, in milliseconds, to use for this request.
     */
    public final void setTimeoutIntervalInMs(final Integer timeoutIntervalInMs) {
        this.timeoutIntervalInMs = timeoutIntervalInMs;
    }

    /**
     * Sets the {@link LocationMode} for this request.
     *

     * The default {@link LocationMode} is set in the client and is by default {@link LocationMode#PRIMARY_ONLY}. You
     * can change the {@link LocationMode} on this request by setting this property. You can also change the value on
     * the {@link ServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the service
     * client will use that {@link LocationMode}.
     *
     * @param locationMode
     *            the locationMode to set
     */
    public void setLocationMode(final LocationMode locationMode) {
        this.locationMode = locationMode;
    }

    /**
     * Sets the maximum execution time to use when making this request.
     *

     * The maximum execution time interval begins at the time that the client begins building the request. The maximum
     * execution time is checked intermittently while uploading data, downloading data, and before executing retries.
     * The service will continue to upload, download, and retry until the maximum execution time is reached. At that
     * time, any partial uploads or downloads will be cancelled and an exception will be thrown.
     *

     * The default maximum execution is set in the client and is by default null, indicating no maximum time. You can
     * change the maximum execution time on this request by setting this property. You can also change the value on the
     * {@link ServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the service
     * client will use the maximum execution time.
     *
     * @param maximumExecutionTimeInMs
     *            The maximum execution time, in milliseconds, to use for this request.
     */
    public void setMaximumExecutionTimeInMs(Integer maximumExecutionTimeInMs) {
        this.maximumExecutionTimeInMs = maximumExecutionTimeInMs;
    }

    /**
     * RESERVED FOR INTERNAL USE.
     *
     * Returns the time at which this operation expires. This is computed by adding the time the operation begins and
     * the maximum execution time and will be null if maximum execution time is null. For more information about maximum
     * execution time, see {@link #setMaximumExecutionTimeInMs(Integer)}.
     *
     * @param operationExpiryTime
     *            the operationExpiryTime to set
     */
    private void setOperationExpiryTimeInMs(final Long operationExpiryTime) {
        this.operationExpiryTime = operationExpiryTime;
    }
}


/**
 * Copyright Microsoft Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.microsoft.azure.storage.core;

import java.io.IOException;
import java.io.InputStream;
import java.util.Stack;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.StorageExtendedErrorInformation;

/**
 * RESERVED FOR INTERNAL USE. A class used to deserialize storage errors.
 */
final class StorageErrorHandler extends DefaultHandler {

    private final Stack<String> elementStack = new Stack<String>();
    private StringBuilder bld = new StringBuilder();

    private final StorageExtendedErrorInformation errorInfo = new StorageExtendedErrorInformation();

    /**
     * Gets the Extended Error information from the response stream.
     *
     * @return the Extended Error information from the response stream
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    public static StorageExtendedErrorInformation getExtendedErrorInformation(final InputStream stream)
            throws SAXException, IOException, ParserConfigurationException {
        SAXParser saxParser = Utility.getSAXParser();
        StorageErrorHandler handler = new StorageErrorHandler();
        saxParser.parse(stream, handler);

        return handler.errorInfo;
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        this.elementStack.push(localName);
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        String currentNode = this.elementStack.pop();

        // if the node popped from the stack and the localName don't match, the xml document is improperly formatted
        if (!localName.equals(currentNode)) {
            throw new SAXException(SR.INVALID_RESPONSE_RECEIVED);
        }

        String parentNode = null;
        if (!this.elementStack.isEmpty()) {
            parentNode = this.elementStack.peek();
        }

        String value = this.bld.toString();
        if (value.isEmpty()) {
            value = null;
        }

        if (Constants.ERROR_ROOT_ELEMENT.equals(parentNode)) {
            if (Constants.ERROR_CODE.equals(currentNode)) {
                this.errorInfo.setErrorCode(value);
            }
            else if (Constants.ERROR_MESSAGE.equals(currentNode)) {
                this.errorInfo.setErrorMessage(value);
            }
            else {
                // get additional details
                this.errorInfo.getAdditionalDetails().put(currentNode, new String[] { value });
            }
        }
        else if (Constants.ERROR_EXCEPTION.equals(parentNode)) {
            // get additional details
            this.errorInfo.getAdditionalDetails().put(currentNode, new String[] { value });
        }

        this.bld = new StringBuilder();
    }

    @Override
    public void characters(char ch[], int start, int length) throws SAXException {
        this.bld.append(ch, start, length);
    }
}


/**
 * Copyright Microsoft Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.microsoft.azure.storage;

import java.net.URI;

import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.Utility;

/**
 * Holds a list of URIs that represents the storage resource.
 */
public class StorageUri {

    private static boolean AreUrisEqual(URI uri1, URI uri2) {
        return uri1 == null ? uri2 == null : uri1.equals(uri2);
    }

    private static void AssertAbsoluteUri(URI uri) {
        if ((uri != null) && !uri.isAbsolute()) {
            String errorMessage = String.format(Utility.LOCALE_US, SR.RELATIVE_ADDRESS_NOT_PERMITTED, uri.toString());
            throw new IllegalArgumentException(errorMessage);
        }
    }

    private URI primaryUri;

    private URI secondaryUri;

    /**
     * Initializes a new instance of the {@link StorageUri} class using the specified URI.
     *
     * @param primaryUri
     *        A java.net.URI object for the primary URI.
     */
    public StorageUri(URI primaryUri) {
        this(primaryUri, null /* secondaryUri */);
    }

    /**
     * Initializes a new instance of the {@link StorageUri} class using the specified primary and secondary URIs.
     *
     * @param primaryUri
     *        A java.net.URI object for the primary URI.
     * @param secondaryUri
     *        A java.net.URI object for the secondary URI.
     */
    public StorageUri(URI primaryUri, URI secondaryUri) {
        if (primaryUri == null && secondaryUri == null) {
            throw new IllegalArgumentException(SR.STORAGE_URI_NOT_NULL);
        }
        if (primaryUri != null && secondaryUri != null) {
            // check query component is equivalent
            if ((primaryUri.getQuery() == null && secondaryUri.getQuery() != null)
                    || (primaryUri.getQuery() != null && !(primaryUri.getQuery().equals(secondaryUri.getQuery())))) {
                throw new IllegalArgumentException(SR.STORAGE_URI_MUST_MATCH);
            }

            boolean primaryPathStyle = Utility.determinePathStyleFromUri(primaryUri);
            boolean secondaryPathStyle = Utility.determinePathStyleFromUri(secondaryUri);

            if (!primaryPathStyle && !secondaryPathStyle) {
                if ((primaryUri.getPath() == null && secondaryUri.getPath() != null)
                        || (primaryUri.getPath() != null && !(primaryUri.getPath().equals(secondaryUri.getPath())))) {
                    throw new IllegalArgumentException(SR.STORAGE_URI_MUST_MATCH);
                }
            }
            else {
                final int maxPrimaryPathSegments = primaryPathStyle ? 3 : 2;
                final int maxSecondaryPathSegments = secondaryPathStyle ? 3 : 2;

                // getPath() on a path-style uri returns /devstore1[/path1/path2], split(3) returns ["","devstore","path1/path2"]
                // getPath() on a regular uri returns [/path1/path2], split(2) returns ["","path1/path2"]
                final String[] primaryPathSegments = primaryUri.getPath().split("/", maxPrimaryPathSegments);
                final String[] secondaryPathSegments = secondaryUri.getPath().split("/", maxSecondaryPathSegments);

                String primaryPath = "";
                if (primaryPathSegments.length == maxPrimaryPathSegments) {
                    primaryPath = primaryPathSegments[primaryPathSegments.length - 1];
                }

                String secondaryPath = "";
                if (secondaryPathSegments.length == maxSecondaryPathSegments) {
                    secondaryPath = secondaryPathSegments[secondaryPathSegments.length - 1];
                }

                if (!primaryPath.equals(secondaryPath)) {
                    throw new IllegalArgumentException(SR.STORAGE_URI_MUST_MATCH);
                }
            }
        }

        this.setPrimaryUri(primaryUri);
        this.setSecondaryUri(secondaryUri);
    }

    @Override
    public boolean equals(Object obj) {
        return this.equals((StorageUri) obj);
    }

    /**
     * Indicates whether a {@link StorageUri} object is equal to the current {@link StorageUri} object.
     *
     * @param other
     *        A reference to a {@link StorageUri} object to compare.
     *
     * @return
     *         true if this object is the same as the other argument; otherwise false.
     */
    public boolean equals(StorageUri other) {
        return (other != null) && StorageUri.AreUrisEqual(this.primaryUri, other.primaryUri)
                && StorageUri.AreUrisEqual(this.secondaryUri, other.secondaryUri);
    }

    /**
     * Gets the endpoint for the primary location for the storage account.
     *
     * @return A java.net.URI object for the primary URI.
     */
    public URI getPrimaryUri() {
        return this.primaryUri;
    }

    /**
     * Gets the endpoint for the secondary location for the storage account.
     *
     * @return A java.net.URI object for the secondary URI.
     */
    public URI getSecondaryUri() {
        return this.secondaryUri;
    }

    /**
     * Gets the URI for a specified {@link StorageLocation}.
     *
     * @param location
     *        The StorageLocation object for which to retrieve a URI.
     *
     * @return
     *        A java.net.URI object for the specified StorageLocation.
     */
    public URI getUri(StorageLocation location) {
        switch (location) {
            case PRIMARY:
                return this.primaryUri;

            case SECONDARY:
                return this.secondaryUri;

            default:
                throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.ARGUMENT_OUT_OF_RANGE_ERROR,
                        "location", location.toString()));
        }
    }

    @Override
    public int hashCode() {
        int hash1 = this.primaryUri != null ? this.primaryUri.hashCode() : 0;
        int hash2 = this.secondaryUri != null ? this.secondaryUri.hashCode() : 0;
        return hash1 ^ hash2;
    }

    /**
     * Sets the endpoint for the primary location of the storage account.
     *
     * @param primaryUri
     *        A java.net.URI object for the primary URI.
     */
    private void setPrimaryUri(URI primaryUri) {
        StorageUri.AssertAbsoluteUri(primaryUri);
        this.primaryUri = primaryUri;
    }

    /**
     * Sets the endpoint for the secondary location of the storage account.
     *
     * @param secondaryUri
     *        A java.net.URI object for the secondary URI.
     */
    private void setSecondaryUri(URI secondaryUri) {
        StorageUri.AssertAbsoluteUri(secondaryUri);
        this.secondaryUri = secondaryUri;
    }

    @Override
    public String toString() {
        return String.format(Utility.LOCALE_US, "Primary = '%s'; Secondary = '%s'", this.primaryUri, this.secondaryUri);
    }

    /**
     * Validate that a URI exists for the specified {@link LocationMode}.
     *
     * @param mode
     *        The LocationMode to validate.
     *
     * @return
     *         true if a URI exists for the specified mode; otherwise false.
     */
    public boolean validateLocationMode(LocationMode mode) {
        switch (mode) {
            case PRIMARY_ONLY:
                return this.primaryUri != null;

            case SECONDARY_ONLY:
                return this.secondaryUri != null;

            default:
                return (this.primaryUri != null) && (this.secondaryUri != null);
        }
    }

    /**
     * Indicates whether this {@link StorageUri} is absolute.
     *
     * @return
     *         true if the current {@link StorageUri} is absolute; otherwise false.
     */
    public boolean isAbsolute() {
        if (this.secondaryUri == null) {
            return this.primaryUri.isAbsolute();
        }
        else {
            return this.primaryUri.isAbsolute() && this.secondaryUri.isAbsolute();
        }
    }

    /**
     * Returns the decoded query component of this {@link StorageUri} object.
     *
     * @return
     *         A String which contains the decoded query component of the current {@link StorageUri},
     *         or null if the query is undefined.
     */
    public String getQuery() {
        return this.primaryUri.getQuery();
    }
}