/**
 * 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.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.HashMap;

import com.microsoft.azure.storage.DoesServiceRequest;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.ResultContinuation;
import com.microsoft.azure.storage.ResultContinuationType;
import com.microsoft.azure.storage.ResultSegment;
import com.microsoft.azure.storage.ServiceClient;
import com.microsoft.azure.storage.ServiceProperties;
import com.microsoft.azure.storage.ServiceStats;
import com.microsoft.azure.storage.StorageCredentials;
import com.microsoft.azure.storage.StorageCredentialsAnonymous;
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.LazySegmentedIterable;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.SegmentedStorageRequest;
import com.microsoft.azure.storage.core.StorageRequest;
import com.microsoft.azure.storage.core.Utility;

/**
 * Provides a service client for accessing the Microsoft Azure Table service.
 *

 * The {@link CloudTableClient} class encapsulates the base URI for the Table service endpoint and the credentials for
 * accessing the storage account, and provides methods to create, delete, list, and query tables, as well as methods to
 * execute operations and queries on table entities. These methods invoke Storage Service REST API operations to make
 * the requests and obtain the results that are returned.
 *

 * A Table service endpoint is the base URI for Table service resources, including the DNS name of the storage account:
 *

 * &nbsp&nbsp&nbsp&nbsphttp://myaccount.table.core.windows.net

 * For more information, see the MSDN topic Addressing Table Service Resources.
 *

 * The credentials can be a combination of the storage account name and a key, or a shared access signature. For more
 * information, see the MSDN topic Authenticating
 * Access to Your Storage Account.
 *
 */
public final class CloudTableClient extends ServiceClient {
    /**
     * Holds the default request option values associated with this Service Client.
     */
    private TableRequestOptions defaultRequestOptions = new TableRequestOptions();

    /**
     * Reserved for internal use. An {@link EntityResolver} that projects table entity data as a String
     * containing the table name.
     */
    private final EntityResolver<String> tableNameResolver = new EntityResolver<String>() {
        @Override
        public String resolve(String partitionKey, String rowKey, Date timeStamp,
                HashMap<String, EntityProperty> properties, String etag) {
            return properties.get(TableConstants.TABLE_NAME).getValueAsString();
        }
    };

    /**
     * Initializes an instance of the CloudTableClient class using a Table service endpoint and
     * storage account credentials.
     *
     * @param baseUri
     *            A java.net.URI object that represents the Table service endpoint used to initialize the
     *            client.
     * @param credentials
     *            A {@link StorageCredentials} object that represents the storage account credentials for access.
     */
    public CloudTableClient(final URI baseUri, StorageCredentials credentials) {
        this(new StorageUri(baseUri), credentials);
    }

    /**
     * Initializes an instance of the CloudTableClientclass using a Table service endpoint and
     * storage account credentials.
     *
     * @param baseUri
     *            A {@link StorageUri} object that represents the Table service endpoint used to initialize the
     *            client.
     * @param credentials
     *            A {@link StorageCredentials} object that represents the storage account credentials for access.
     */
    public CloudTableClient(final StorageUri baseUri, StorageCredentials credentials) {
        super(baseUri, credentials);
        if (credentials == null || credentials.getClass().equals(StorageCredentialsAnonymous.class)) {
            throw new IllegalArgumentException(SR.STORAGE_CREDENTIALS_NULL_OR_ANONYMOUS);
        }
        TableRequestOptions.applyDefaults(this.defaultRequestOptions);
    }

    /**
     * Gets a {@link CloudTable} object with the specified name.
     *
     * @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".
     *
     * @return A reference to a {@link CloudTable} object.
     *
     * @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
     */
    public CloudTable getTableReference(final String tableName) throws URISyntaxException, StorageException {
        return new CloudTable(tableName, this);
    }

    /**
     * Lists the table names in the storage account.
     *

     * This method invokes the Query Tables
     * REST API to list the table names, using the Table service endpoint and storage account credentials of this
     * instance.
     *
     * @return An Iterable collection of the table names in the storage account retrieved lazily.
     */
    @DoesServiceRequest
    public Iterable<String> listTables() {
        return this.listTables(null);
    }

    /**
     * Lists the table names in the storage account that match the specified prefix.
     *

     * This method invokes the Query Tables
     * REST API to list the table names that match the prefix, using the Table service endpoint and storage account
     * credentials of this instance.
     *
     * @param prefix
     *            A String containing the prefix to match on table names to return.
     *
     * @return
     *         An Iterable collection of the table names in the storage account retrieved lazily that match
     *         the specified prefix.
     */
    @DoesServiceRequest
    public Iterable<String> listTables(final String prefix) {
        return this.listTables(prefix, null /* options */, null /* opContext */);
    }

    /**
     * Lists the table names in the storage account that match the specified prefix, using the specified
     * {@link TableRequestOptions} and {@link OperationContext}.
     *

     * This method invokes the Query Tables
     * REST API to list the table names that match the prefix, 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 prefix
     *            A String containing the prefix to match on table names to return.
     * @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
     *         An Iterable collection of the table names in the storage account retrieved lazily that match
     *         the specified
     *         prefix.
     */
    @SuppressWarnings("unchecked")
    @DoesServiceRequest
    public Iterable<String> listTables(final String prefix, final TableRequestOptions options,
            final OperationContext opContext) {
        return (Iterable<String>) this.generateIteratorForQuery(this.generateListTablesQuery(prefix),
                this.tableNameResolver, options, opContext);
    }

    /**
     * Lists the table names in the storage account in segmented mode. This method allows listing of tables to be
     * resumed after returning a partial set of results, using information returned by the server in the
     * {@link ResultSegment} object.
     *

     * This method invokes the Query Tables
     * REST API to list the table names, using the Table service endpoint and storage account credentials of this
     * instance.
     *
     * @return
     *         A {@link ResultSegment} of String objects containing table names in the storage account.
     *
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public ResultSegment<String> listTablesSegmented() throws StorageException {
        return this.listTablesSegmented(null);
    }

    /**
     * Lists the table names in the storage account that match the specified prefix in segmented mode. This method
     * allows listing of tables to be resumed after returning a partial set of results, using information returned by
     * the server in the {@link ResultSegment} object.
     *

     * This method invokes the Query Tables
     * REST API to list the table names that match the prefix, using the Table service endpoint and storage account
     * credentials of this instance.
     *
     * @param prefix
     *            A String containing the prefix to match on table names to return.
     *
     * @return
     *         A {@link ResultSegment} of String objects containing table names matching the prefix in the
     *         storage account.
     *
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    @DoesServiceRequest
    public ResultSegment<String> listTablesSegmented(final String prefix) throws StorageException {
        return this.listTablesSegmented(prefix, null /* maxResults */, null /* continuationToken */,
                null /* options */, null /* opContext */);
    }

    /**
     * Lists up to the specified maximum of the table names in the storage account that match the specified prefix in a
     * resumable mode with the specified {@link ResultContinuation} continuation token, using the specified
     * {@link TableRequestOptions} and {@link OperationContext}. This method allows listing of tables to be resumed
     * after returning a page of results, using information returned by the server in the {@link ResultSegment} object.
     *

     * This method invokes the Query Tables
     * REST API to list the table names that match the prefix, 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 prefix
     *            A String containing the prefix to match on table names to return.
     * @param maxResults
     *            The maximum number of table names to return in the {@link ResultSegment}. If this parameter is null,
     *            the query will list up to the maximum 1,000 results.
     * @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} of String objects containing table names in the storage account.
     *
     * @throws StorageException
     *             if a storage service error occurred during the operation.
     */
    @SuppressWarnings("unchecked")
    @DoesServiceRequest
    public ResultSegment<String> listTablesSegmented(final String prefix, final Integer maxResults,
            final ResultContinuation continuationToken, final TableRequestOptions options,
            final OperationContext opContext) throws StorageException {
        if (null != maxResults) {
            Utility.assertGreaterThanOrEqual("maxResults", maxResults, 1);
        }

        return (ResultSegment<String>) this.executeQuerySegmentedImpl(
                this.generateListTablesQuery(prefix).take(maxResults), this.tableNameResolver, continuationToken,
                options, opContext);
    }

    /**
     * Reserved for internal use. Generates a query to list table names with the given prefix.
     *
     * @param prefix
     *            A String containing the prefix to match on table names to return.
     * @return
     *         A {@link TableQuery} instance for listing table names with the specified prefix.
     */
    private TableQuery<TableServiceEntity> generateListTablesQuery(final String prefix) {
        TableQuery<TableServiceEntity> listQuery = TableQuery.<TableServiceEntity> from(TableServiceEntity.class);
        listQuery.setSourceTableName(TableConstants.TABLES_SERVICE_TABLES_NAME);

        if (!Utility.isNullOrEmpty(prefix)) {
            // Append Max char to end '{' is 1 + 'z' in AsciiTable > uppperBound = prefix + '{'
            final String prefixFilter = String.format("(%s ge '%s') and (%s lt '%s{')", TableConstants.TABLE_NAME,
                    prefix, TableConstants.TABLE_NAME, prefix);

            listQuery = listQuery.where(prefixFilter);
        }

        return listQuery;
    }

    /**
     * Reserved for internal use. Executes a segmented query operation using the specified retry and timeout policies.
     *
     * @param queryToExecute
     *            The {@link TableQuery} to execute.
     * @param resolver
     *            An {@link EntityResolver} instance which creates a projection of the table query result entities into
     *            the specified type R. Pass null to return the results as the table entity
     *            type.
     * @param continuationToken
     *            The {@link ResultContinuation} to pass with the operation to resume a query, if any. Pass
     *            null for an initial 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 a collection of the query results specialized for the
     *         {@link TableEntity} or {@link EntityResolver} type returned by the query.
     * @throws StorageException
     *             if a Storage service error occurs.
     */
    protected <T extends TableEntity, R> ResultSegment<?> executeQuerySegmentedImpl(final TableQuery<T> queryToExecute,
            final EntityResolver<R> resolver, final ResultContinuation continuationToken, TableRequestOptions options,
            OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

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

        Utility.assertContinuationType(continuationToken, ResultContinuationType.TABLE);

        SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest();
        segmentedRequest.setToken(continuationToken);

        return ExecutionEngine.executeWithRetry(this, queryToExecute,
                this.executeQuerySegmentedWithResolverCoreImpl(queryToExecute, resolver, options, segmentedRequest),
                options.getRetryPolicyFactory(), opContext);
    }

    private <T extends TableEntity, R> StorageRequest<CloudTableClient, TableQuery<T>, ResultSegment<T>> executeQuerySegmentedCoreImpl(
            final TableQuery<T> queryToExecute, final EntityResolver<R> resolver, final TableRequestOptions options,
            final SegmentedStorageRequest segmentedRequest) {

        if (resolver == null) {
            Utility.assertNotNull(SR.QUERY_REQUIRES_VALID_CLASSTYPE_OR_RESOLVER, queryToExecute.getClazzType());
        }

        final StorageRequest<CloudTableClient, TableQuery<T>, ResultSegment<T>> getRequest = new StorageRequest<CloudTableClient, TableQuery<T>, ResultSegment<T>>(
                options, this.getStorageUri()) {

            @Override
            public void setRequestLocationMode() {
                this.setRequestLocationMode(Utility.getListingLocationMode(segmentedRequest.getToken()));
            }

            @Override
            public HttpURLConnection buildRequest(CloudTableClient client, TableQuery<T> queryRef,
                    OperationContext context) throws Exception {
                return TableRequest.query(client.getTransformedEndPoint(context).getUri(this.getCurrentLocation()),
                        options, queryToExecute.generateQueryBuilder(), context, queryToExecute.getSourceTableName(),
                        null/* identity */, segmentedRequest.getToken());
            }

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

            @Override
            public ResultSegment<T> preProcessResponse(TableQuery<T> queryRef, CloudTableClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    throw TableServiceException.generateTableServiceException(this.getResult(), null,
                            this.getConnection().getErrorStream(), options.getTablePayloadFormat());
                }

                return null;
            }

            @SuppressWarnings("unchecked")
            @Override
            public ResultSegment<T> postProcessResponse(HttpURLConnection connection, TableQuery<T> queryRef,
                    CloudTableClient client, OperationContext context, ResultSegment<T> storageObject) throws Exception {
                ODataPayload<T> clazzResponse = null;

                InputStream inStream = connection.getInputStream();

                clazzResponse = (ODataPayload<T>) TableDeserializer.parseQueryResponse(inStream, options,
                        queryToExecute.getClazzType(), null, context);

                final ResultContinuation nextToken = TableResponse.getTableContinuationFromResponse(connection);
                if (nextToken != null) {
                    nextToken.setTargetLocation(this.getResult().getTargetLocation());
                }

                // Note, setting the token on the SegmentedStorageRequest is
                // key, this is how the iterator will share the token across executions
                segmentedRequest.setToken(nextToken);

                return new ResultSegment<T>(clazzResponse.results,
                        queryToExecute.getTakeCount() == null ? clazzResponse.results.size()
                                : queryToExecute.getTakeCount(), nextToken);
            }

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

        return getRequest;
    }

    private <T extends TableEntity, R> StorageRequest<CloudTableClient, TableQuery<T>, ResultSegment<R>> executeQuerySegmentedWithResolverCoreImpl(
            final TableQuery<T> queryToExecute, final EntityResolver<R> resolver, final TableRequestOptions options,
            final SegmentedStorageRequest segmentedRequest) {

        if (resolver == null) {
            Utility.assertNotNull(SR.QUERY_REQUIRES_VALID_CLASSTYPE_OR_RESOLVER, queryToExecute.getClazzType());
        }

        final StorageRequest<CloudTableClient, TableQuery<T>, ResultSegment<R>> getRequest = new StorageRequest<CloudTableClient, TableQuery<T>, ResultSegment<R>>(
                options, this.getStorageUri()) {

            @Override
            public void setRequestLocationMode() {
                this.setRequestLocationMode(Utility.getListingLocationMode(segmentedRequest.getToken()));
            }

            @Override
            public HttpURLConnection buildRequest(CloudTableClient client, TableQuery<T> queryRef,
                    OperationContext context) throws Exception {
                return TableRequest.query(client.getTransformedEndPoint(context).getUri(this.getCurrentLocation()),
                        options, queryToExecute.generateQueryBuilder(), context, queryToExecute.getSourceTableName(),
                        null/* identity */, segmentedRequest.getToken());
            }

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

            @Override
            public ResultSegment<R> preProcessResponse(TableQuery<T> queryRef, CloudTableClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    throw TableServiceException.generateTableServiceException(this.getResult(), null,
                            this.getConnection().getErrorStream(), options.getTablePayloadFormat());
                }

                return null;
            }

            @SuppressWarnings("unchecked")
            @Override
            public ResultSegment<R> postProcessResponse(HttpURLConnection connection, TableQuery<T> queryRef,
                    CloudTableClient client, OperationContext context, ResultSegment<R> storageObject) throws Exception {
                ODataPayload<R> resolvedResponse = null;

                InputStream inStream = connection.getInputStream();

                resolvedResponse = (ODataPayload<R>) TableDeserializer.parseQueryResponse(inStream, options,
                        queryToExecute.getClazzType(), resolver, context);

                final ResultContinuation nextToken = TableResponse.getTableContinuationFromResponse(connection);
                if (nextToken != null) {
                    nextToken.setTargetLocation(this.getResult().getTargetLocation());
                }

                // Note, setting the token on the SegmentedStorageRequest is
                // key, this is how the iterator will share the token across executions
                segmentedRequest.setToken(nextToken);

                return new ResultSegment<R>(resolvedResponse.results,
                        queryToExecute.getTakeCount() == null ? resolvedResponse.results.size()
                                : queryToExecute.getTakeCount(), nextToken);

            }

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

        return getRequest;
    }

    protected final StorageUri getTransformedEndPoint(final OperationContext opContext) throws URISyntaxException,
            StorageException {
        return this.getCredentials().transformUri(this.getStorageUri(), opContext);
    }

    /**
     * Reserved for internal use. Generates an iterator for a segmented query operation.
     *
     * @param queryRef
     *            The {@link TableQuery} to execute.
     * @param resolver
     *            An {@link EntityResolver} instance which creates a projection of the table query result entities into
     *            the specified type R. Pass null to return the results as the table entity
     *            type.
     * @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
     *         An instance of Iterable specialized for the {@link TableEntity} or {@link EntityResolver}
     *         type returned by the query.
     */
    protected <T extends TableEntity, R> Iterable<?> generateIteratorForQuery(final TableQuery<T> queryRef,
            final EntityResolver<R> resolver, TableRequestOptions options, OperationContext opContext) {

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

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

        SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest();

        if (resolver == null) {
            return new LazySegmentedIterable<CloudTableClient, TableQuery<T>, T>(this.executeQuerySegmentedCoreImpl(
                    queryRef, resolver, options, segmentedRequest), this, queryRef, options.getRetryPolicyFactory(),
                    opContext);
        }
        else {
            return new LazySegmentedIterable<CloudTableClient, TableQuery<T>, R>(
                    this.executeQuerySegmentedWithResolverCoreImpl(queryRef, resolver, options, segmentedRequest),
                    this, queryRef, options.getRetryPolicyFactory(), opContext);
        }
    }

    /**
     * Queries the service for the {@link ServiceStats}.
     *
     * @return {@link ServiceStats} for the given storage service
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ServiceStats getServiceStats() throws StorageException {
        return this.getServiceStats(null /* options */, null /* opContext */);
    }

    /**
     * Queries the given storage service for the {@link ServiceStats}.
     *
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @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 {@link ServiceStats} for the given storage service
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ServiceStats getServiceStats(TableRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

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

        return ExecutionEngine.executeWithRetry(this, null, this.getServiceStatsImpl(options, true),
                options.getRetryPolicyFactory(), opContext);
    }

    /**
     * Retrieves the current {@link ServiceProperties} for the given storage service. This includes Logging,
     * HourMetrics, MinuteMetrics and CORS configurations.
     *
     * @return the {@link ServiceProperties} object representing the current configuration of the service.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final ServiceProperties downloadServiceProperties() throws StorageException {
        return this.downloadServiceProperties(null /* options */, null /* opContext */);
    }

    /**
     * Retrieves the current {@link ServiceProperties} for the given storage service. This includes Logging,
     * HourMetrics, MinuteMetrics and CORS configurations.
     *
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @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 the {@link ServiceProperties} object representing the current configuration of the service.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final ServiceProperties downloadServiceProperties(TableRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

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

        return ExecutionEngine.executeWithRetry(this, null, this.downloadServicePropertiesImpl(options, true),
                options.getRetryPolicyFactory(), opContext);
    }

    /**
     * Uploads a new {@link ServiceProperties} configuration to the given storage service. This includes Logging,
     * HourMetrics, MinuteMetrics and CORS configurations.
     *
     * @param properties
     *            The {@link ServiceProperties} to upload.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadServiceProperties(final ServiceProperties properties) throws StorageException {
        this.uploadServiceProperties(properties, null /* options */, null /* opContext */);
    }

    /**
     * Uploads a new {@link ServiceProperties} configuration to the given storage service. This includes Logging,
     * HourMetrics, MinuteMetrics and CORS configurations.
     *
     * @param properties
     *            The {@link ServiceProperties} to upload.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @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 uploadServiceProperties(final ServiceProperties properties, TableRequestOptions options,
            OperationContext opContext) throws StorageException {
        if (!Utility.isNullOrEmpty(properties.getDefaultServiceVersion())) {
            throw new IllegalArgumentException(SR.DEFAULT_SERVICE_VERSION_ONLY_SET_FOR_BLOB_SERVICE);
        }

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

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

        Utility.assertNotNull("properties", properties);

        ExecutionEngine.executeWithRetry(this, null,
                this.uploadServicePropertiesImpl(properties, options, opContext, true),
                options.getRetryPolicyFactory(), opContext);
    }

    /**
     * Gets the {@link TableRequestOptions} that is used for requests associated with this CloudTableClient
     *
     * @return
     *         The {@link TableRequestOptions} object containing the values used by this CloudTableClient
     */
    @Override
    public TableRequestOptions getDefaultRequestOptions() {
        return this.defaultRequestOptions;
    }

    /**
     * Sets the {@link TableRequestOptions} that is used for any table accessed with this CloudTableClient
     * object.
     *
     * @param defaultRequestOptions
     *            The TableRequestOptions to use.
     */
    public void setDefaultRequestOptions(TableRequestOptions defaultRequestOptions) {
        Utility.assertNotNull("defaultRequestOptions", defaultRequestOptions);
        this.defaultRequestOptions = defaultRequestOptions;
    }

    /**
     * Indicates whether path-style URIs are used.
     *
     * @return true if path-style URIs are used; otherwise false.
     */
    @Override
    protected boolean isUsePathStyleUris() {
        return super.isUsePathStyleUris();
    }
}


/**
 * 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.analytics;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.UUID;

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

/**
 * Represents a Storage Analytics Log record entry.
 */
public class LogRecord {

    /**
     * Holds the date format for the RequestStartTime field.
     */
    protected static final SimpleDateFormat REQUEST_START_TIME_FORMAT = new SimpleDateFormat(
            "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'");

    /**
     * Holds the date format for the LastModifiedTime field.
     */
    protected static final SimpleDateFormat LAST_MODIFIED_TIME_FORMAT = new SimpleDateFormat(
            "E, dd-MMM-yy HH:mm:ss 'GMT'");

    private String versionNumber;

    private Date requestStartTime;

    private String operationType;

    private String requestStatus;

    private String httpStatusCode;

    private Integer endToEndLatencyInMS;

    private Integer serverLatencyInMS;

    private String authenticationType;

    private String requesterAccountName;

    private String ownerAccountName;

    private String serviceType;

    private URI requestUrl;

    private String requestedObjectKey;

    private UUID requestIdHeader;

    private Integer operationCount;

    private String requesterIPAddress;

    private String requestVersionHeader;

    private Long requestHeaderSize;

    private Long requestPacketSize;

    private Long responseHeaderSize;

    private Long responsePacketSize;

    private Long requestContentLength;

    private String requestMD5;

    private String serverMD5;

    private String eTagIdentifier;

    private Date lastModifiedTime;

    private String conditionsUsed;

    private String userAgentHeader;

    private String referrerHeader;

    private String clientRequestId;

    /**
     * Initializes a new instance of the LogRecord class.
     */
    protected LogRecord() {
    }

    /**
     * Initializes a new instance of the LogRecord class using a LogRecordStreamReader to populate.
     *
     * @param reader
     *            the LogRecordStreamReader to use to populate the LogRecord.
     * @throws IOException
     * @throws ParseException
     * @throws URISyntaxException
     */
    protected LogRecord(LogRecordStreamReader reader) throws IOException, ParseException, URISyntaxException {
        LAST_MODIFIED_TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
        REQUEST_START_TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));

    	Utility.assertNotNull("reader", reader);
        this.versionNumber = reader.readString();
        Utility.assertNotNullOrEmpty("versionNumber", this.versionNumber);

        if (this.versionNumber.equals("1.0")) {
            this.populateVersion1Log(reader);
        }
        else {
            throw new IllegalArgumentException(String.format(SR.LOG_VERSION_UNSUPPORTED, this.versionNumber));
        }
    }

    /**
     * Populates a LogRecord using the Version 1.0 schema and the given LogRecordStreamReader.
     *
     * @param reader
     *            the LogRecordStreamReader to use to populate the LogRecord.
     * @throws IOException
     * @throws ParseException
     * @throws URISyntaxException
     */
    private void populateVersion1Log(LogRecordStreamReader reader) throws IOException, ParseException,
            URISyntaxException {
        this.requestStartTime = reader.readDate(LogRecord.REQUEST_START_TIME_FORMAT);
        this.operationType = reader.readString();
        this.requestStatus = reader.readString();
        this.httpStatusCode = reader.readString();
        this.endToEndLatencyInMS = reader.readInteger();
        this.serverLatencyInMS = reader.readInteger();
        this.authenticationType = reader.readString();
        this.requesterAccountName = reader.readString();
        this.ownerAccountName = reader.readString();
        this.serviceType = reader.readString();
        this.requestUrl = reader.readUri();
        this.requestedObjectKey = reader.readQuotedString();
        this.requestIdHeader = reader.readUuid();
        this.operationCount = reader.readInteger();
        this.requesterIPAddress = reader.readString();
        this.requestVersionHeader = reader.readString();
        this.requestHeaderSize = reader.readLong();
        this.requestPacketSize = reader.readLong();
        this.responseHeaderSize = reader.readLong();
        this.responsePacketSize = reader.readLong();
        this.requestContentLength = reader.readLong();
        this.requestMD5 = reader.readQuotedString();
        this.serverMD5 = reader.readQuotedString();
        this.eTagIdentifier = reader.readQuotedString();
        this.lastModifiedTime = reader.readDate(LogRecord.LAST_MODIFIED_TIME_FORMAT);
        this.conditionsUsed = reader.readQuotedString();
        this.userAgentHeader = reader.readQuotedString();
        this.referrerHeader = reader.readQuotedString();
        this.clientRequestId = reader.readQuotedString();
        reader.endCurrentRecord();

    }

    /**
     * Gets the version of Storage Analytics Logging used to record the entry.
     *
     * @return
     *         a String containing the VersionNumber value
     */
    public String getVersionNumber() {
        return this.versionNumber;
    }

    /**
     * Gets the time in UTC when the request was received by Storage Analytics.
     *
     * @return
     *         a String containing the RequestStartTime value
     */
    public Date getRequestStartTime() {
        return this.requestStartTime;
    }

    /**
     * Gets the type of REST operation performed.
     *
     * @return
     *         a String containing the OperationType value
     */
    public String getOperationType() {
        return this.operationType;
    }

    /**
     * Gets the status of the requested operation.
     *
     * @return
     *         a String containing the RequestStatus value
     */
    public String getRequestStatus() {
        return this.requestStatus;
    }

    /**
     * Gets the HTTP status code for the request. If the request is interrupted, this value may be set to Unknown.
     *
     * @return
     *         a String containing the HttpStatusCode value
     */
    public String getHttpStatusCode() {
        return this.httpStatusCode;
    }

    /**
     * Gets the total time in milliseconds to perform the requested operation, including the time to read the
     * incoming request and send the response to the requester.
     *
     * @return
     *         an Integer containing the EndToEndLatencyInMS value
     */
    public Integer getEndToEndLatencyInMS() {
        return this.endToEndLatencyInMS;
    }

    /**
     * Gets the total time in milliseconds to perform the requested operation. This value does not include network
     * latency (the time to read the incoming request and send the response to the requester).
     *
     * @return
     *         an Integer containing the ServerLatencyInMS value
     */
    public Integer getServerLatencyInMS() {
        return this.serverLatencyInMS;
    }

    /**
     * Gets whether the request was authenticated, anonymous, or used Shared Access Signature (SAS).
     *
     * @return
     *         a String containing the AuthenticationType value
     */
    public String getAuthenticationType() {
        return this.authenticationType;
    }

    /**
     * Gets the account name making the request, if the request is authenticated. This field will be null for
     * anonymous and SAS requests.
     *
     * @return
     *         a String containing the RequesterAccountName value
     */
    public String getRequesterAccountName() {
        return this.requesterAccountName;
    }

    /**
     * Gets the account name of the service owner.
     *
     * @return
     *         a String containing the OwnerAccountName value
     */
    public String getOwnerAccountName() {
        return this.ownerAccountName;
    }

    /**
     * Gets the requested storage service: blob, table, or queue.
     *
     * @return
     *         a String containing the ServiceType value
     */
    public String getServiceType() {
        return this.serviceType;
    }

    /**
     * Gets the complete URL of the request.
     *
     * @return
     *         a URI containing the RequestUrl value
     */
    public URI getRequestUrl() {
        return this.requestUrl;
    }

    /**
     * Gets the key of the requested object as an encoded string. This field will always use the account name, even
     * if a custom domain name has been configured.
     *
     * @return
     *         a String containing the RequestedObjectKey value
     */
    public String getRequestedObjectKey() {
        return this.requestedObjectKey;
    }

    /**
     * Gets the request ID assigned by the storage service. This is equivalent to the value of the x-ms-request-id
     * header.
     *
     * @return
     *         a UUID containing the RequestIdHeader value
     */
    public UUID getRequestIdHeader() {
        return this.requestIdHeader;
    }

    /**
     * Gets the number of each logged operation for a request, using an index of zero. Some requests require more
     * than one operation, such as Copy Blob, though most perform just one operation.
     *
     * @return
     *         an Integer containing the OperationCount value
     */
    public Integer getOperationCount() {
        return this.operationCount;
    }

    /**
     * Gets the IP address and port of the requester.
     *
     * @return
     *         a String containing the RequesterIPAddress value
     */
    public String getRequesterIPAddress() {
        return this.requesterIPAddress;
    }

    /**
     * Gets the storage service version specified when the request was made. This is equivalent to the value of the
     * x-ms-version header.
     *
     * @return
     *         a String containing the RequestVersionHeader value
     */
    public String getRequestVersionHeader() {
        return this.requestVersionHeader;
    }

    /**
     * Gets the size of the request header, in bytes. If a request is unsuccessful, this value may be null.
     *
     * @return
     *         a Long containing the RequestHeaderSize value
     */
    public Long getRequestHeaderSize() {
        return this.requestHeaderSize;
    }

    /**
     * Gets the size of the request packets read by the storage service, in bytes. If a request is unsuccessful, this
     * value may be null.
     *
     * @return
     *         a Long containing the RequestPacketSize value
     */
    public Long getRequestPacketSize() {
        return this.requestPacketSize;
    }

    /**
     * Gets the size of the response header, in bytes. If a request is unsuccessful, this value may be null.
     *
     * @return
     *         a Long containing the ResponseHeaderSize value
     */
    public Long getResponseHeaderSize() {
        return this.responseHeaderSize;
    }

    /**
     * Gets the size of the response packets written by the storage service, in bytes. If a request is unsuccessful,
     * this value may be null.
     *
     * @return
     *         a Long containing the ResponsePacketSize value
     */
    public Long getResponsePacketSize() {
        return this.responsePacketSize;
    }

    /**
     * Gets the value of the Content-Length header for the request sent to the storage service. If the request was
     * successful, this value is equal to request-packet-size. If a request is unsuccessful, this value may not
     * be equal to request-packet-size, or it may be null.
     *
     * @return
     *         a Long containing the RequestContentLength value
     */
    public Long getRequestContentLength() {
        return this.requestContentLength;
    }

    /**
     * Gets the value of either the Content-MD5 header or the x-ms-content-md5 header in the request as an encoded
     * string. The MD5 hash value specified in this field represents the content in the request. This field can
     * be null.
     *
     * @return
     *         an encoded String containing the RequestMD5 value
     */
    public String getRequestMD5() {
        return this.requestMD5;
    }

    /**
     * Gets the value of the MD5 hash calculated by the storage service as an encoded string.
     *
     * @return
     *         an encoded String containing the ServerMD5 value
     */
    public String getServerMD5() {
        return this.serverMD5;
    }

    /**
     * Gets the ETag identifier for the returned object as an encoded string.
     *
     * @return
     *         an encoded String containing the ETagIdentifier value
     */
    public String getETagIdentifier() {
        return this.eTagIdentifier;
    }

    /**
     * Gets the Last Modified Time (LMT) for the returned object as an encoded string. This field is null for
     * operations that can return multiple objects.
     *
     * @return
     *         a Date containing the LastModifiedTime value
     */
    public Date getLastModifiedTime() {
        return this.lastModifiedTime;
    }

    /**
     * Gets conditions used, as an encoded string semicolon-separated list in the form of ConditionName=value .
     *
     * @return
     *         an encoded String containing the ConditionsUsed value
     */
    public String getConditionsUsed() {
        return this.conditionsUsed;
    }

    /**
     * Gets the User-Agent header value as an encoded string.
     *
     * @return
     *         an encoded String containing the UserAgentHeader value
     */
    public String getUserAgentHeader() {
        return this.userAgentHeader;
    }

    /**
     * Gets the Referrer header value as an encoded string.
     *
     * @return
     *         an encoded String containing the ReferrerHeader value
     */
    public String getReferrerHeader() {
        return this.referrerHeader;
    }

    /**
     * Gets the x-ms-client-request-id header value included in the request.
     *
     * @return
     *         an encoded String containing the ClientRequestId value
     */
    public String getClientRequestId() {
        return this.clientRequestId;
    }

    /**
     * @param versionNumber
     *            the versionNumber to set
     */
    protected void setVersionNumber(String versionNumber) {
        this.versionNumber = versionNumber;
    }

    /**
     * @param requestStartTime
     *            the requestStartTime to set
     */
    protected void setRequestStartTime(Date requestStartTime) {
        this.requestStartTime = requestStartTime;
    }

    /**
     * @param operationType
     *            the operationType to set
     */
    protected void setOperationType(String operationType) {
        this.operationType = operationType;
    }

    /**
     * @param requestStatus
     *            the requestStatus to set
     */
    protected void setRequestStatus(String requestStatus) {
        this.requestStatus = requestStatus;
    }

    /**
     * @param httpStatusCode
     *            the httpStatusCode to set
     */
    protected void setHttpStatusCode(String httpStatusCode) {
        this.httpStatusCode = httpStatusCode;
    }

    /**
     * @param endToEndLatencyInMS
     *            the endToEndLatencyInMS to set
     */
    protected void setEndToEndLatencyInMS(Integer endToEndLatencyInMS) {
        this.endToEndLatencyInMS = endToEndLatencyInMS;
    }

    /**
     * @param serverLatencyInMS
     *            the serverLatencyInMS to set
     */
    protected void setServerLatencyInMS(Integer serverLatencyInMS) {
        this.serverLatencyInMS = serverLatencyInMS;
    }

    /**
     * @param authenticationType
     *            the authenticationType to set
     */
    protected void setAuthenticationType(String authenticationType) {
        this.authenticationType = authenticationType;
    }

    /**
     * @param requesterAccountName
     *            the requesterAccountName to set
     */
    protected void setRequesterAccountName(String requesterAccountName) {
        this.requesterAccountName = requesterAccountName;
    }

    /**
     * @param ownerAccountName
     *            the ownerAccountName to set
     */
    protected void setOwnerAccountName(String ownerAccountName) {
        this.ownerAccountName = ownerAccountName;
    }

    /**
     * @param serviceType
     *            the serviceType to set
     */
    protected void setServiceType(String serviceType) {
        this.serviceType = serviceType;
    }

    /**
     * @param requestUrl
     *            the requestUrl to set
     */
    protected void setRequestUrl(URI requestUrl) {
        this.requestUrl = requestUrl;
    }

    /**
     * @param requestedObjectKey
     *            the requestedObjectKey to set
     */
    protected void setRequestedObjectKey(String requestedObjectKey) {
        this.requestedObjectKey = requestedObjectKey;
    }

    /**
     * @param requestIdHeader
     *            the requestIdHeader to set
     */
    protected void setRequestIdHeader(UUID requestIdHeader) {
        this.requestIdHeader = requestIdHeader;
    }

    /**
     * @param operationCount
     *            the operationCount to set
     */
    protected void setOperationCount(Integer operationCount) {
        this.operationCount = operationCount;
    }

    /**
     * @param requesterIPAddress
     *            the requesterIPAddress to set
     */
    protected void setRequesterIPAddress(String requesterIPAddress) {
        this.requesterIPAddress = requesterIPAddress;
    }

    /**
     * @param requestVersionHeader
     *            the requestVersionHeader to set
     */
    protected void setRequestVersionHeader(String requestVersionHeader) {
        this.requestVersionHeader = requestVersionHeader;
    }

    /**
     * @param requestHeaderSize
     *            the requestHeaderSize to set
     */
    protected void setRequestHeaderSize(Long requestHeaderSize) {
        this.requestHeaderSize = requestHeaderSize;
    }

    /**
     * @param requestPacketSize
     *            the requestPacketSize to set
     */
    protected void setRequestPacketSize(Long requestPacketSize) {
        this.requestPacketSize = requestPacketSize;
    }

    /**
     * @param responseHeaderSize
     *            the responseHeaderSize to set
     */
    protected void setResponseHeaderSize(Long responseHeaderSize) {
        this.responseHeaderSize = responseHeaderSize;
    }

    /**
     * @param responsePacketSize
     *            the responsePacketSize to set
     */
    protected void setResponsePacketSize(Long responsePacketSize) {
        this.responsePacketSize = responsePacketSize;
    }

    /**
     * @param requestContentLength
     *            the requestContentLength to set
     */
    protected void setRequestContentLength(Long requestContentLength) {
        this.requestContentLength = requestContentLength;
    }

    /**
     * @param requestMD5
     *            the requestMD5 to set
     */
    protected void setRequestMD5(String requestMD5) {
        this.requestMD5 = requestMD5;
    }

    /**
     * @param serverMD5
     *            the serverMD5 to set
     */
    protected void setServerMD5(String serverMD5) {
        this.serverMD5 = serverMD5;
    }

    /**
     * @param eTagIdentifier
     *            the eTagIdentifier to set
     */
    protected void setETagIdentifier(String eTagIdentifier) {
        this.eTagIdentifier = eTagIdentifier;
    }

    /**
     * @param lastModifiedTime
     *            the lastModifiedTime to set
     */
    protected void setLastModifiedTime(Date lastModifiedTime) {
        this.lastModifiedTime = lastModifiedTime;
    }

    /**
     * @param conditionsUsed
     *            the conditionsUsed to set
     */
    protected void setConditionsUsed(String conditionsUsed) {
        this.conditionsUsed = conditionsUsed;
    }

    /**
     * @param userAgentHeader
     *            the userAgentHeader to set
     */
    protected void setUserAgentHeader(String userAgentHeader) {
        this.userAgentHeader = userAgentHeader;
    }

    /**
     * @param referrerHeader
     *            the referrerHeader to set
     */
    protected void setReferrerHeader(String referrerHeader) {
        this.referrerHeader = referrerHeader;
    }

    /**
     * @param clientRequestId
     *            the clientRequestId to set
     */
    protected void setClientRequestId(String clientRequestId) {
        this.clientRequestId = clientRequestId;
    }
}


/**
 * 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.net.URISyntaxException;
import java.util.HashMap;
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.StorageException;
import com.microsoft.azure.storage.core.ListResponse;
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 queues.
 */
final class QueueListHandler extends DefaultHandler {

    private final ListResponse<CloudQueue> response = new ListResponse<CloudQueue>();

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

    private final CloudQueueClient serviceClient;

    private String queueName;
    private HashMap<String, String> metadata;

    private QueueListHandler(CloudQueueClient serviceClient) {
        this.serviceClient = serviceClient;
    }

    /**
     * Parses the input stream containing the response body of the list queues request result and populates the class
     * data.
     *
     * @param stream
     *            The InputStream object to deserialize from.
     * @param serviceClient
     *            A {@link CloudQueueClient} object associated with the storage
     *            service.
     *
     * @throws IOException
     * @throws SAXException
     * @throws ParserConfigurationException
     */
    public static ListResponse<CloudQueue> getQueues(final InputStream stream, final CloudQueueClient serviceClient)
            throws SAXException, IOException, ParserConfigurationException {
        SAXParser saxParser = Utility.getSAXParser();
        QueueListHandler handler = new QueueListHandler(serviceClient);
        saxParser.parse(stream, handler);

        return handler.response;
    }

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

        if (QueueConstants.QUEUE_ELEMENT.equals(localName)) {
            this.queueName = Constants.EMPTY_STRING;
            this.metadata = new HashMap<String, 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 parentNode = null;
        if (!this.elementStack.isEmpty()) {
            parentNode = this.elementStack.peek();
        }

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

        if (QueueConstants.QUEUE_ELEMENT.equals(currentNode)) {
            CloudQueue queue;
            try {
                queue = this.serviceClient.getQueueReference(this.queueName);
                queue.setMetadata(this.metadata);

                this.response.getResults().add(queue);
            }
            catch (URISyntaxException e) {
                throw new SAXException(e);
            }
            catch (StorageException e) {
                throw new SAXException(e);
            }
        }
        else if (ListResponse.ENUMERATION_RESULTS.equals(parentNode)) {
            if (Constants.PREFIX_ELEMENT.equals(currentNode)) {
                this.response.setPrefix(value);
            }
            else if (Constants.MARKER_ELEMENT.equals(currentNode)) {
                this.response.setMarker(value);
            }
            else if (Constants.NEXT_MARKER_ELEMENT.equals(currentNode)) {
                this.response.setNextMarker(value);
            }
            else if (Constants.MAX_RESULTS_ELEMENT.equals(currentNode)) {
                this.response.setMaxResults(Integer.parseInt(value));
            }
        }
        else if (Constants.METADATA_ELEMENT.equals(parentNode)) {
            this.metadata.put(currentNode, value);
        }
        else if (QueueConstants.QUEUE_ELEMENT.equals(parentNode)) {
            if (Constants.NAME_ELEMENT.equals(currentNode)) {
                this.queueName = 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.io.IOException;
import java.net.HttpURLConnection;
import java.net.SocketException;

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

/**
 * Represents an exception for the Microsoft Azure storage service.
 */
public class StorageException extends Exception {
    /**
     * Represents the serialization version number.
     */
    private static final long serialVersionUID = 7972747254288274928L;

    /**
     * RESERVED FOR INTERNAL USE. Translates the specified exception into a storage exception.
     *
     * @param cause
     *            An Exception object that represents the exception to translate.
     *
     * @return A StorageException object that represents translated exception.
     */
    public static StorageException translateClientException(final Exception cause) {
        return new StorageException("Client error",
                "A Client side exception occurred, please check the inner exception for details",
                Constants.HeaderConstants.HTTP_UNUSED_306, null, cause);
    }

    /**
     * RESERVED FOR INTERNAL USE. Translates the specified exception into a storage exception.
     *
     * @param request
     *            An HttpURLConnection object that represents the request whose exception is being
     *            translated.
     * @param cause
     *            An Exception object that represents the exception to translate.
     *
     * @return A StorageException object that represents translated exception.
     */
    public static StorageException translateException(final StorageRequest<?, ?, ?> request, final Exception cause,
            final OperationContext opContext) {
        if (request == null || request.getConnection() == null) {
            return translateClientException(cause);
        }

        if (cause instanceof SocketException) {
            String message = cause == null ? Constants.EMPTY_STRING : cause.getMessage();
            return new StorageException(StorageErrorCode.SERVICE_INTERNAL_ERROR.toString(),
                    "An unknown failure occurred : ".concat(message), HttpURLConnection.HTTP_INTERNAL_ERROR,
                    null, cause);
        }

        StorageException translatedException = null;

        String responseMessage = null;
        int responseCode = 0;
        try {
            responseCode = request.getConnection().getResponseCode();
            responseMessage = request.getConnection().getResponseMessage();
        } catch (final IOException e) {
            // ignore errors
        }

        if (responseMessage == null) {
            responseMessage = Constants.EMPTY_STRING;
        }

        StorageExtendedErrorInformation extendedError = request.parseErrorDetails();
        if (extendedError != null) {
            // 1. If extended information is available use it
            translatedException = new StorageException(extendedError.getErrorCode(), responseMessage, responseCode,
                    extendedError, cause);
        } else {
            // 2. If extended information is unavailable, translate exception based
            // on status code
            translatedException = translateFromHttpStatus(responseCode, responseMessage, cause);
        }

        if (translatedException != null) {
            Utility.logHttpError(translatedException, opContext);
            return translatedException;
        } else {
            return new StorageException(StorageErrorCode.SERVICE_INTERNAL_ERROR.toString(),
                    "The server encountered an unknown failure: ".concat(responseMessage),
                    HttpURLConnection.HTTP_INTERNAL_ERROR, null, cause);
        }
    }

    /**
     * Translates the specified HTTP status code into a storage exception.
     *
     * @param statusCode
     *            The HTTP status code returned by the operation.
     * @param statusDescription
     *            A String that represents the status description.
     * @param inner
     *            An Exception object that represents a reference to the initial exception, if one exists.
     *
     * @return A StorageException object that represents translated exception.
     **/
    protected static StorageException translateFromHttpStatus(final int statusCode, final String statusDescription,
            final Exception inner) {
        String errorCode;
        switch (statusCode) {
        case HttpURLConnection.HTTP_FORBIDDEN:
            errorCode = StorageErrorCode.ACCESS_DENIED.toString();
            break;
        case HttpURLConnection.HTTP_GONE:
        case HttpURLConnection.HTTP_NOT_FOUND:
            errorCode = StorageErrorCode.RESOURCE_NOT_FOUND.toString();
            break;
        case 416:
        case HttpURLConnection.HTTP_BAD_REQUEST:
            // 416: RequestedRangeNotSatisfiable - No corresponding enum in HttpURLConnection
            errorCode = StorageErrorCode.BAD_REQUEST.toString();
            break;

        case HttpURLConnection.HTTP_PRECON_FAILED:
        case HttpURLConnection.HTTP_NOT_MODIFIED:
            errorCode = StorageErrorCode.CONDITION_FAILED.toString();
            break;

        case HttpURLConnection.HTTP_CONFLICT:
            errorCode = StorageErrorCode.RESOURCE_ALREADY_EXISTS.toString();
            break;

        case HttpURLConnection.HTTP_UNAVAILABLE:
            errorCode = StorageErrorCode.SERVER_BUSY.toString();
            break;

        case HttpURLConnection.HTTP_GATEWAY_TIMEOUT:
            errorCode = StorageErrorCode.SERVICE_TIMEOUT.toString();
            break;

        case HttpURLConnection.HTTP_INTERNAL_ERROR:
            errorCode = StorageErrorCode.SERVICE_INTERNAL_ERROR.toString();
            break;

        case HttpURLConnection.HTTP_NOT_IMPLEMENTED:
            errorCode = StorageErrorCode.NOT_IMPLEMENTED.toString();
            break;

        case HttpURLConnection.HTTP_BAD_GATEWAY:
            errorCode = StorageErrorCode.BAD_GATEWAY.toString();
            break;

        case HttpURLConnection.HTTP_VERSION:
            errorCode = StorageErrorCode.HTTP_VERSION_NOT_SUPPORTED.toString();
            break;
        default:
            errorCode = null;
        }

        if (errorCode == null) {
            return null;
        } else {
            return new StorageException(errorCode, statusDescription, statusCode, null, inner);
        }
    }

    /**
     * Represents the error code returned by the operation.
     */
    protected String errorCode;

    /**
     * Represents the extended error information returned by the operation.
     *
     * @see StorageExtendedErrorInformation
     */
    protected StorageExtendedErrorInformation extendedErrorInformation;

    /**
     * Represents the HTTP status code returned by the operation.
     */
    private final int httpStatusCode;

    /**
     * Creates an instance of the StorageException class using the specified parameters.
     *
     * @param errorCode
     *            A String that represents the error code returned by the operation.
     * @param message
     *            A String that represents the error message returned by the operation.
     * @param statusCode
     *            The HTTP status code returned by the operation.
     * @param extendedErrorInfo
     *            A {@link StorageExtendedErrorInformation} object that represents the extended error information
     *            returned by the operation.
     * @param innerException
     *            An Exception object that represents a reference to the initial exception, if one exists.
     *
     * @see StorageExtendedErrorInformation
     */
    public StorageException(final String errorCode, final String message, final int statusCode,
            final StorageExtendedErrorInformation extendedErrorInfo, final Exception innerException) {
        super(message, innerException);
        this.errorCode = errorCode;
        this.httpStatusCode = statusCode;
        this.extendedErrorInformation = extendedErrorInfo;
    }

    /**
     * Gets the error code returned by the operation.
     *
     * @return the errorCode
     */
    public String getErrorCode() {
        return this.errorCode;
    }

    /**
     * Gets the extended error information returned by the operation.
     *
     * @return the extendedErrorInformation
     */
    public StorageExtendedErrorInformation getExtendedErrorInformation() {
        return this.extendedErrorInformation;
    }

    /**
     * Gets the HTTP status code returned by the operation.
     *
     * @return the httpStatusCode
     */
    public int getHttpStatusCode() {
        return this.httpStatusCode;
    }
}


/**
 * 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.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;

import com.microsoft.azure.storage.DoesServiceRequest;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.ResultContinuation;
import com.microsoft.azure.storage.ResultContinuationType;
import com.microsoft.azure.storage.ResultSegment;
import com.microsoft.azure.storage.ServiceClient;
import com.microsoft.azure.storage.ServiceProperties;
import com.microsoft.azure.storage.ServiceStats;
import com.microsoft.azure.storage.StorageCredentials;
import com.microsoft.azure.storage.StorageCredentialsAnonymous;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.StorageUri;
import com.microsoft.azure.storage.blob.BlobRequestOptions;
import com.microsoft.azure.storage.blob.CloudBlobClient;
import com.microsoft.azure.storage.core.ExecutionEngine;
import com.microsoft.azure.storage.core.LazySegmentedIterable;
import com.microsoft.azure.storage.core.ListResponse;
import com.microsoft.azure.storage.core.ListingContext;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.SegmentedStorageRequest;
import com.microsoft.azure.storage.core.StorageRequest;
import com.microsoft.azure.storage.core.Utility;

/**
 * Provides a service client for accessing the Microsoft Azure Queue service.
 */
public final class CloudQueueClient extends ServiceClient {

    /**
     * Holds the default request option values associated with this Service Client.
     */
    private QueueRequestOptions defaultRequestOptions = new QueueRequestOptions();

    /**
     * Initializes a new instance of the CloudQueueClient class using the specified Queue service endpoint
     * and account credentials.
     *
     * @param baseUri
     *            A java.net.URI object that represents the Queue service endpoint used to create the
     *            client.
     * @param credentials
     *            A {@link StorageCredentials} object that represents the account credentials.
     */
    public CloudQueueClient(final URI baseUri, final StorageCredentials credentials) {
        this(new StorageUri(baseUri, null), credentials);
    }

    /**
     * Initializes a new instance of the CloudQueueClient class using the specified Queue service endpoint
     * and account credentials.
     *
     * @param baseUri
     *            A {@link StorageUri} object that represents the Queue service endpoint used to create the
     *            client.
     * @param credentials
     *            A {@link StorageCredentials} object that represents the account credentials.
     */
    public CloudQueueClient(final StorageUri baseUri, final StorageCredentials credentials) {
        super(baseUri, credentials);
        if (credentials == null || credentials.getClass().equals(StorageCredentialsAnonymous.class)) {
            throw new IllegalArgumentException(SR.STORAGE_CREDENTIALS_NULL_OR_ANONYMOUS);
        }
        QueueRequestOptions.applyDefaults(this.defaultRequestOptions);
    }

    /**
     * Gets a {@link CloudQueue} object with the specified name.
     *
     * @param queueName
     *            The name of the queue, which must adhere to queue naming rules. The queue name should not include any
     *            path separator characters (/).
     *            Queue names must be lowercase, between 3-63 characters long and must start with a letter or number.
     *            Queue names may contain only letters, numbers, and the dash (-) character.
     * @return A reference to a {@link CloudQueue} object.
     * @throws URISyntaxException
     *             If the resource URI constructed based on the queueName is invalid.
     * @throws StorageException
     *             If a storage service error occurred.
     * @see Naming Queues and Metadata
     */
    public CloudQueue getQueueReference(final String queueName) throws URISyntaxException, StorageException {
        return new CloudQueue(queueName, this);
    }

    /**
     * Gets an iterable collection of queues for this queue service client.
     *
     * @return An iterable collection of {@link CloudQueue} objects retrieved lazily that
     *         represent the queues in this client.
     */
    @DoesServiceRequest
    public Iterable<CloudQueue> listQueues() {
        return this.listQueues(null, QueueListingDetails.NONE, null, null);
    }

    /**
     * Returns an iterable collection of queues whose names begin with the
     * specified prefix in this Queue service client.
     *
     * @param prefix
     *            A String that represents the queue name prefix.
     *
     * @return An iterable collection of {@link CloudQueue} objects retrieved lazily that
     *         represent the queues in this client whose names begin with the
     *         specified prefix.
     */
    @DoesServiceRequest
    public Iterable<CloudQueue> listQueues(final String prefix) {
        return this.listQueues(prefix, QueueListingDetails.NONE, null, null);
    }

    /**
     * Returns an iterable collection of queues whose names begin with the
     * specified prefix for this Queue service client, using the specified
     * details setting, request options, and operation context.
     *
     * @param prefix
     *            A String that represents the queue name prefix.
     * @param detailsIncluded
     *            A {@link QueueListingDetails} value that indicates whether
     *            queue metadata will be returned.
     * @param options
     *            A {@link QueueRequestOptions} object that specifies any
     *            additional options for the request. Specifying null 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 An iterable collection of {@link CloudQueue} objects retrieved lazily that
     *         represents the specified queues for this client.
     */
    @DoesServiceRequest
    public Iterable<CloudQueue> listQueues(final String prefix, final QueueListingDetails detailsIncluded,
            QueueRequestOptions options, OperationContext opContext) {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = QueueRequestOptions.populateAndApplyDefaults(options, this);

        SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest();
        return new LazySegmentedIterable<CloudQueueClient, Void, CloudQueue>(this.listQueuesSegmentedImpl(prefix,
                detailsIncluded, null, options, segmentedRequest), this, null, options.getRetryPolicyFactory(), opContext);
    }

    /**
     * Gets a result segment of an iterable collection of queues for this Queue
     * service client.
     *
     * @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() throws StorageException {
        return this.listQueuesSegmented(null, QueueListingDetails.NONE, null, null, null, null);
    }

    /**
     * Gets a result segment of an iterable collection of queues whose names
     * begin with the specified prefix for this Queue service client.
     *
     * @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) throws StorageException {
        return this.listQueuesSegmented(prefix, QueueListingDetails.NONE, null, null, null, null);
    }

    /**
     * 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 String 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 results to retrieve.  If null or greater
     *            than 5000, the server will return up to 5,000 items.  Must be at least 1.
     * @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 null 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 Integer maxResults,
            final ResultContinuation continuationToken, QueueRequestOptions options, OperationContext opContext)
            throws StorageException {

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

        opContext.initialize();
        options = QueueRequestOptions.populateAndApplyDefaults(options, this);

        SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest();
        segmentedRequest.setToken(continuationToken);
        return ExecutionEngine.executeWithRetry(this, null,
                this.listQueuesSegmentedImpl(prefix, detailsIncluded, maxResults, options, segmentedRequest),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudQueueClient, Void, ResultSegment<CloudQueue>> listQueuesSegmentedImpl(
            final String prefix, final QueueListingDetails detailsIncluded, final Integer maxResults,
            final QueueRequestOptions options, final SegmentedStorageRequest segmentedRequest) {

        Utility.assertContinuationType(segmentedRequest.getToken(), ResultContinuationType.QUEUE);
        final ListingContext listingContext = new ListingContext(prefix, maxResults);
        final StorageRequest<CloudQueueClient, Void, ResultSegment<CloudQueue>> getRequest = new StorageRequest<CloudQueueClient, Void, ResultSegment<CloudQueue>>(
                options, this.getStorageUri()) {

            @Override
            public void setRequestLocationMode() {
                this.setRequestLocationMode(Utility.getListingLocationMode(segmentedRequest.getToken()));
            }

            @Override
            public HttpURLConnection buildRequest(CloudQueueClient client, Void parentObject, OperationContext context)
                    throws Exception {
                listingContext.setMarker(segmentedRequest.getToken() != null ? segmentedRequest.getToken()
                        .getNextMarker() : null);
                return QueueRequest.list(
                        credentials.transformUri(client.getStorageUri().getUri(this.getCurrentLocation())),
                        options, context, listingContext, detailsIncluded);
            }

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

            @Override
            public ResultSegment<CloudQueue> preProcessResponse(Void parentObject, CloudQueueClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                }

                return null;
            }

            @Override
            public ResultSegment<CloudQueue> postProcessResponse(HttpURLConnection connection, Void queue,
                    CloudQueueClient client, OperationContext context, ResultSegment<CloudQueue> storageObject)
                    throws Exception {
                final ListResponse<CloudQueue> response = QueueListHandler.getQueues(connection.getInputStream(),
                        client);

                ResultContinuation newToken = null;

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

                final ResultSegment<CloudQueue> resSegment = new ResultSegment<CloudQueue>(response.getResults(),
                        response.getMaxResults(), newToken);

                // Important for listQueues because this is required by the lazy iterator between executions.
                segmentedRequest.setToken(resSegment.getContinuationToken());
                return resSegment;
            }
        };

        return getRequest;
    }

    /**
     * Queries the service for the {@link ServiceStats}.
     *
     * @return {@link ServiceStats} for the given storage service
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ServiceStats getServiceStats() throws StorageException {
        return this.getServiceStats(null /* options */, null /* opContext */);
    }

    /**
     * Queries the service for the {@link ServiceStats}.
     *
     * @param options
     *            A {@link QueueRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudQueueClient}).
     * @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 {@link ServiceStats} for the given storage service
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ServiceStats getServiceStats(QueueRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = QueueRequestOptions.populateAndApplyDefaults(options, this);

        return ExecutionEngine.executeWithRetry(this, null, this.getServiceStatsImpl(options, false),
                options.getRetryPolicyFactory(), opContext);
    }

    /**
     * Retrieves the current {@link ServiceProperties} for the given storage service. This includes Logging,
     * HourMetrics, MinuteMetrics and CORS configurations.
     *
     * @return the {@link ServiceProperties} object representing the current configuration of the service.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final ServiceProperties downloadServiceProperties() throws StorageException {
        return this.downloadServiceProperties(null /* options */, null /* opContext */);
    }

    /**
     * Retrieves the current {@link ServiceProperties} for the given storage service. This includes Logging,
     * HourMetrics, MinuteMetrics and CORS configurations.
     *
     * @param options
     *            A {@link QueueRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudQueueClient}).
     * @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 the {@link ServiceProperties} object representing the current configuration of the service.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final ServiceProperties downloadServiceProperties(QueueRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = QueueRequestOptions.populateAndApplyDefaults(options, this);

        return ExecutionEngine.executeWithRetry(this, null, this.downloadServicePropertiesImpl(options, false),
                options.getRetryPolicyFactory(), opContext);
    }

    /**
     * Uploads a new {@link ServiceProperties} configuration to the given storage service. This includes Logging,
     * HourMetrics, MinuteMetrics and CORS configurations.
     *
     * @param properties
     *            The {@link ServiceProperties} to upload.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadServiceProperties(final ServiceProperties properties) throws StorageException {
        this.uploadServiceProperties(properties, null /* options */, null /* opContext */);
    }

    /**
     * Uploads a new {@link ServiceProperties} configuration to the given storage service. This includes Logging,
     * HourMetrics, MinuteMetrics and CORS configurations.
     *
     * @param properties
     *            The {@link ServiceProperties} to upload.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            null will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @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 uploadServiceProperties(final ServiceProperties properties, QueueRequestOptions options,
            OperationContext opContext) throws StorageException {
        if (!Utility.isNullOrEmpty(properties.getDefaultServiceVersion())) {
            throw new IllegalArgumentException(SR.DEFAULT_SERVICE_VERSION_ONLY_SET_FOR_BLOB_SERVICE);
        }

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

        opContext.initialize();
        options = QueueRequestOptions.populateAndApplyDefaults(options, this);

        Utility.assertNotNull("properties", properties);

        ExecutionEngine.executeWithRetry(this, null,
                this.uploadServicePropertiesImpl(properties, options, opContext, false),
                options.getRetryPolicyFactory(), opContext);
    }

    /**
     * @return the usePathStyleUris
     */
    @Override
    protected boolean isUsePathStyleUris() {
        return super.isUsePathStyleUris();
    }

    /**
     * Gets the {@link QueueRequestOptions} that is used for requests associated with this CloudQueueClient
     *
     * @return
     *         The {@link QueueRequestOptions} object containing the values used by this CloudQueueClient
     */
    @Override
    public QueueRequestOptions getDefaultRequestOptions() {
        return this.defaultRequestOptions;
    }

    /**
     * Sets the {@link QueueRequestOptions} that is used for any queue accessed with this CloudQueueClient
     * object.
     *
     * @param defaultRequestOptions
     *            The QueueRequestOptions to use.
     */
    public void setDefaultRequestOptions(QueueRequestOptions defaultRequestOptions) {
        Utility.assertNotNull("defaultRequestOptions", defaultRequestOptions);
        this.defaultRequestOptions = defaultRequestOptions;
    }
}