public final class Constants {
    /**
     * Defines constants for ServiceProperties requests.
     */
    public static class AnalyticsConstants {

        /**
         * The XML element for the CORS Rule AllowedHeaders
         */
        public static final String ALLOWED_HEADERS_ELEMENT = "AllowedHeaders";

        /**
         * The XML element for the CORS Rule AllowedMethods
         */
        public static final String ALLOWED_METHODS_ELEMENT = "AllowedMethods";

        /**
         * The XML element for the CORS Rule AllowedOrigins
         */
        public static final String ALLOWED_ORIGINS_ELEMENT = "AllowedOrigins";

        /**
         * The XML element for the CORS
         */
        public static final String CORS_ELEMENT = "Cors";

        /**
         * The XML element for the CORS Rules
         */
        public static final String CORS_RULE_ELEMENT = "CorsRule";

        /**
         * The XML element for the RetentionPolicy Days.
         */
        public static final String DAYS_ELEMENT = "Days";

        /**
         * The XML element for the Default Service Version.
         */
        public static final String DEFAULT_SERVICE_VERSION = "DefaultServiceVersion";

        /**
         * The XML element for the Logging Delete type.
         */
        public static final String DELETE_ELEMENT = "Delete";

        /**
         * The XML element for the RetentionPolicy Enabled.
         */
        public static final String ENABLED_ELEMENT = "Enabled";

        /**
         * The XML element for the CORS Rule ExposedHeaders
         */
        public static final String EXPOSED_HEADERS_ELEMENT = "ExposedHeaders";

        /**
         * The XML element for the Hour Metrics
         */
        public static final String HOUR_METRICS_ELEMENT = "HourMetrics";

        /**
         * The XML element for the Metrics IncludeAPIs.
         */
        public static final String INCLUDE_APIS_ELEMENT = "IncludeAPIs";

        /**
         * Constant for the logs container.
         */
        public static final String LOGS_CONTAINER = "$logs";

        /**
         * The XML element for the Logging
         */
        public static final String LOGGING_ELEMENT = "Logging";

        /**
         * The XML element for the CORS Rule MaxAgeInSeconds
         */
        public static final String MAX_AGE_IN_SECONDS_ELEMENT = "MaxAgeInSeconds";

        /**
         * Constant for the blob capacity metrics table.
         */
        public static final String METRICS_CAPACITY_BLOB = "$MetricsCapacityBlob";

        /**
         * Constant for the blob service primary location hourly metrics table.
         */
        public static final String METRICS_HOUR_PRIMARY_TRANSACTIONS_BLOB = "$MetricsHourPrimaryTransactionsBlob";

        /**
         * Constant for the file service primary location hourly metrics table.
         */
        public static final String METRICS_HOUR_PRIMARY_TRANSACTIONS_FILE = "$MetricsHourPrimaryTransactionsFile";

        /**
         * Constant for the table service primary location hourly metrics table.
         */
        public static final String METRICS_HOUR_PRIMARY_TRANSACTIONS_TABLE = "$MetricsHourPrimaryTransactionsTable";

        /**
         * Constant for the queue service primary location hourly metrics table.
         */
        public static final String METRICS_HOUR_PRIMARY_TRANSACTIONS_QUEUE = "$MetricsHourPrimaryTransactionsQueue";

        /**
         * Constant for the blob service primary location minute metrics table.
         */
        public static final String METRICS_MINUTE_PRIMARY_TRANSACTIONS_BLOB = "$MetricsMinutePrimaryTransactionsBlob";

        /**
         * Constant for the file service primary location minute metrics table.
         */
        public static final String METRICS_MINUTE_PRIMARY_TRANSACTIONS_FILE = "$MetricsMinutePrimaryTransactionsFile";

        /**
         * Constant for the table service primary location minute metrics table.
         */
        public static final String METRICS_MINUTE_PRIMARY_TRANSACTIONS_TABLE = "$MetricsMinutePrimaryTransactionsTable";

        /**
         * Constant for the queue service primary location minute metrics table.
         */
        public static final String METRICS_MINUTE_PRIMARY_TRANSACTIONS_QUEUE = "$MetricsMinutePrimaryTransactionsQueue";

        /**
         * Constant for the blob service secondary location hourly metrics table.
         */
        public static final String METRICS_HOUR_SECONDARY_TRANSACTIONS_BLOB = "$MetricsHourSecondaryTransactionsBlob";

        /**
         * Constant for the file service secondary location hourly metrics table.
         */
        public static final String METRICS_HOUR_SECONDARY_TRANSACTIONS_FILE = "$MetricsHourSecondaryTransactionsFile";

        /**
         * Constant for the table service secondary location hourly metrics table.
         */
        public static final String METRICS_HOUR_SECONDARY_TRANSACTIONS_TABLE = "$MetricsHourSecondaryTransactionsTable";

        /**
         * Constant for the queue service secondary location hourly metrics table.
         */
        public static final String METRICS_HOUR_SECONDARY_TRANSACTIONS_QUEUE = "$MetricsHourSecondaryTransactionsQueue";

        /**
         * Constant for the blob service secondary location minute metrics table.
         */
        public static final String METRICS_MINUTE_SECONDARY_TRANSACTIONS_BLOB = "$MetricsMinuteSecondaryTransactionsBlob";

        /**
         * Constant for the file service secondary location minute metrics table.
         */
        public static final String METRICS_MINUTE_SECONDARY_TRANSACTIONS_FILE = "$MetricsMinuteSecondaryTransactionsFile";

        /**
         * Constant for the table service secondary location minute metrics table.
         */
        public static final String METRICS_MINUTE_SECONDARY_TRANSACTIONS_TABLE = "$MetricsMinuteSecondaryTransactionsTable";

        /**
         * Constant for the queue service secondary location minute metrics table.
         */
        public static final String METRICS_MINUTE_SECONDARY_TRANSACTIONS_QUEUE = "$MetricsMinuteSecondaryTransactionsQueue";

        /**
         * The XML element for the Minute Metrics
         */
        public static final String MINUTE_METRICS_ELEMENT = "MinuteMetrics";

        /**
         * The XML element for the Logging Read type.
         */
        public static final String READ_ELEMENT = "Read";

        /**
         * The XML element for the RetentionPolicy.
         */
        public static final String RETENTION_POLICY_ELEMENT = "RetentionPolicy";

        /**
         * The XML element for the StorageServiceProperties
         */
        public static final String STORAGE_SERVICE_PROPERTIES_ELEMENT = "StorageServiceProperties";

        /**
         * The XML element for the StorageServiceStats
         */
        public static final String STORAGE_SERVICE_STATS = "StorageServiceStats";

        /**
         * The XML element for the Version
         */
        public static final String VERSION_ELEMENT = "Version";

        /**
         * The XML element for the Logging Write type.
         */
        public static final String WRITE_ELEMENT = "Write";

    }

    /**
     * Defines constants for use with HTTP headers.
     */
    public static class HeaderConstants {
        /**
         * The Accept header.
         */
        public static final String ACCEPT = "Accept";

        /**
         * The Accept-Charset header.
         */
        public static final String ACCEPT_CHARSET = "Accept-Charset";

        /**
         * The Accept-Encoding header.
         */
        public static final String ACCEPT_ENCODING = "Accept-Encoding";

        /**
         * The Authorization header.
         */
        public static final String AUTHORIZATION = "Authorization";

        /**
         * The format string for specifying ranges with only begin offset.
         */
        public static final String BEGIN_RANGE_HEADER_FORMAT = "bytes=%d-";
        
        /**
         * The format string for specifying the blob append offset.
         */
        public static final String BLOB_APPEND_OFFSET = PREFIX_FOR_STORAGE_HEADER + "blob-append-offset";
        
        /**
         * The header that specifies committed block count.
         */
        public static final String BLOB_COMMITTED_BLOCK_COUNT = PREFIX_FOR_STORAGE_HEADER + "blob-committed-block-count";
        
        /**
         * The header that specifies blob sequence number.
         */
        public static final String BLOB_SEQUENCE_NUMBER = PREFIX_FOR_STORAGE_HEADER + "blob-sequence-number";
        
        /**
         * The CacheControl header.
         */
        public static final String CACHE_CONTROL = "Cache-Control";

        /**
         * The header that specifies blob caching control.
         */
        public static final String CACHE_CONTROL_HEADER = PREFIX_FOR_STORAGE_HEADER + "blob-cache-control";

        /**
         * The header that indicates the client request ID.
         */
        public static final String CLIENT_REQUEST_ID_HEADER = PREFIX_FOR_STORAGE_HEADER + "client-request-id";

        /**
         * The ContentDisposition header.
         */
        public static final String CONTENT_DISPOSITION = "Content-Disposition";

        /**
         * The ContentEncoding header.
         */
        public static final String CONTENT_ENCODING = "Content-Encoding";

        /**
         * The ContentLangauge header.
         */
        public static final String CONTENT_LANGUAGE = "Content-Language";

        /**
         * The ContentLength header.
         */
        public static final String CONTENT_LENGTH = "Content-Length";

        /**
         * The ContentMD5 header.
         */
        public static final String CONTENT_MD5 = "Content-MD5";

        /**
         * The ContentRange header.
         */
        public static final String CONTENT_RANGE = "Content-Range";

        /**
         * The ContentType header.
         */
        public static final String CONTENT_TYPE = "Content-Type";

        /**
         * The value of the copy action header that signifies an abort operation.
         */
        public static final String COPY_ACTION_ABORT = "abort";

        /**
         * Header that specifies the copy action.
         */
        public static final String COPY_ACTION_HEADER = PREFIX_FOR_STORAGE_HEADER + "copy-action";

        /**
         * The header that specifies copy completion time.
         */
        public static final String COPY_COMPLETION_TIME = PREFIX_FOR_STORAGE_HEADER + "copy-completion-time";

        /**
         * The header that specifies copy id.
         */
        public static final String COPY_ID = PREFIX_FOR_STORAGE_HEADER + "copy-id";

        /**
         * The header that specifies copy progress.
         */
        public static final String COPY_PROGRESS = PREFIX_FOR_STORAGE_HEADER + "copy-progress";

        /**
         * The header that specifies copy source.
         */
        public static final String COPY_SOURCE = PREFIX_FOR_STORAGE_HEADER + "copy-source";

        /**
         * The header for copy source.
         */
        public static final String COPY_SOURCE_HEADER = PREFIX_FOR_STORAGE_HEADER + "copy-source";

        /**
         * The header that specifies copy status.
         */
        public static final String COPY_STATUS = PREFIX_FOR_STORAGE_HEADER + "copy-status";

        /**
         * The header that specifies copy status description.
         */
        public static final String COPY_STATUS_DESCRIPTION = PREFIX_FOR_STORAGE_HEADER + "copy-status-description";

        /**
         * The header that specifies copy type.
         */
        public static final String INCREMENTAL_COPY = PREFIX_FOR_STORAGE_HEADER + "incremental-copy";

        /**
         * The header that specifies the snapshot ID of the last successful incremental snapshot.
         */
        public static final String COPY_DESTINATION_SNAPSHOT_ID = PREFIX_FOR_STORAGE_HEADER + "copy-destination-snapshot";

        /**
         * The header that specifies the date.
         */
        public static final String DATE = PREFIX_FOR_STORAGE_HEADER + "date";

        /**
         * The header to delete snapshots.
         */
        public static final String DELETE_SNAPSHOT_HEADER = PREFIX_FOR_STORAGE_HEADER + "delete-snapshots";

        /**
         * The ETag header.
         */
        public static final String ETAG = "ETag";

        /**
         * An unused HTTP code used internally to indicate a non-http related failure when constructing
         * {@link StorageException} objects
         */
        public static final int HTTP_UNUSED_306 = 306;

        /**
         * The blob append position equal header.
         */
        public static final String IF_APPEND_POSITION_EQUAL_HEADER = PREFIX_FOR_STORAGE_HEADER + "blob-condition-appendpos";
        
        /**
         * The IfMatch header.
         */
        public static final String IF_MATCH = "If-Match";
        
        /**
         * The blob maxsize condition header.
         */
        public static final String IF_MAX_SIZE_LESS_THAN_OR_EQUAL = PREFIX_FOR_STORAGE_HEADER + "blob-condition-maxsize";

        /**
         * The IfModifiedSince header.
         */
        public static final String IF_MODIFIED_SINCE = "If-Modified-Since";

        /**
         * The IfNoneMatch header.
         */
        public static final String IF_NONE_MATCH = "If-None-Match";

        /**
         * The IfUnmodifiedSince header.
         */
        public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
        
        /**
         * The blob sequence number less than or equal condition header.
         */
        public static final String IF_SEQUENCE_NUMBER_LESS_THAN_OR_EQUAL = PREFIX_FOR_STORAGE_HEADER + "if-sequence-number-le";
        
        /**
         * The blob sequence number less than condition header.
         */
        public static final String IF_SEQUENCE_NUMBER_LESS_THAN = PREFIX_FOR_STORAGE_HEADER + "if-sequence-number-lt";
        
        /**
         * The blob sequence number equal condition header.
         */
        public static final String IF_SEQUENCE_NUMBER_EQUAL = PREFIX_FOR_STORAGE_HEADER + "if-sequence-number-eq";

        /**
         * Specifies snapshots are to be included.
         */
        public static final String INCLUDE_SNAPSHOTS_VALUE = "include";
        
        /**
         * The header that specifies the lease action to perform
         */
        public static final String LEASE_ACTION_HEADER = PREFIX_FOR_STORAGE_HEADER + "lease-action";

        /**
         * The header that specifies the break period of a lease
         */
        public static final String LEASE_BREAK_PERIOD_HEADER = PREFIX_FOR_STORAGE_HEADER + "lease-break-period";

        /**
         * The header that specifies lease duration.
         */
        public static final String LEASE_DURATION = PREFIX_FOR_STORAGE_HEADER + "lease-duration";

        /**
         * The header that specifies lease ID.
         */
        public static final String LEASE_ID_HEADER = PREFIX_FOR_STORAGE_HEADER + "lease-id";

        /**
         * The header that specifies lease state.
         */
        public static final String LEASE_STATE = PREFIX_FOR_STORAGE_HEADER + "lease-state";

        /**
         * The header that specifies lease status.
         */
        public static final String LEASE_STATUS = PREFIX_FOR_STORAGE_HEADER + "lease-status";

        /**
         * The header that specifies the remaining lease time
         */
        public static final String LEASE_TIME_HEADER = PREFIX_FOR_STORAGE_HEADER + "lease-time";

        /**
         * The header that specifies the pop receipt.
         */
        public static final String POP_RECEIPT_HEADER = PREFIX_FOR_STORAGE_HEADER + "popreceipt";

        /**
         * The header prefix for metadata.
         */
        public static final String PREFIX_FOR_STORAGE_METADATA = "x-ms-meta-";

        /**
         * The header prefix for properties.
         */
        public static final String PREFIX_FOR_STORAGE_PROPERTIES = "x-ms-prop-";

        /**
         * The header that specifies the proposed lease ID for a leasing operation
         */
        public static final String PROPOSED_LEASE_ID_HEADER = PREFIX_FOR_STORAGE_HEADER + "proposed-lease-id";

        /**
         * The Range header.
         */
        public static final String RANGE = "Range";

        /**
         * The header that specifies if the request will populate the ContentMD5 header for range gets.
         */
        public static final String RANGE_GET_CONTENT_MD5 = PREFIX_FOR_STORAGE_HEADER + "range-get-content-md5";

        /**
         * The format string for specifying ranges.
         */
        public static final String RANGE_HEADER_FORMAT = "bytes=%d-%d";

        /**
         * The header that indicates the request ID.
         */
        public static final String REQUEST_ID_HEADER = PREFIX_FOR_STORAGE_HEADER + "request-id";

        /**
         * The header field value received that indicates which server was accessed
         */
        public static final String SERVER = "Server";

        /**
         * The header that specifies whether a resource is fully encrypted server-side
         */
        public static final String SERVER_ENCRYPTED = PREFIX_FOR_STORAGE_HEADER + "server-encrypted";

        /**
         * The header that acknowledges data used for a write operation is encrypted server-side
         */
        public static final String SERVER_REQUEST_ENCRYPTED = PREFIX_FOR_STORAGE_HEADER + "request-server-encrypted";

        /**
         * The header that specifies the snapshot ID.
         */
        public static final String SNAPSHOT_ID_HEADER = PREFIX_FOR_STORAGE_HEADER + "snapshot";

        /**
         * The header for the If-Match condition.
         */
        public static final String SOURCE_IF_MATCH_HEADER = PREFIX_FOR_STORAGE_HEADER + "source-if-match";

        /**
         * The header for the If-Modified-Since condition.
         */
        public static final String SOURCE_IF_MODIFIED_SINCE_HEADER = PREFIX_FOR_STORAGE_HEADER
                + "source-if-modified-since";

        /**
         * The header for the If-None-Match condition.
         */
        public static final String SOURCE_IF_NONE_MATCH_HEADER = PREFIX_FOR_STORAGE_HEADER + "source-if-none-match";

        /**
         * The header for the If-Unmodified-Since condition.
         */
        public static final String SOURCE_IF_UNMODIFIED_SINCE_HEADER = PREFIX_FOR_STORAGE_HEADER
                + "source-if-unmodified-since";

        /**
         * The header for the source lease id.
         */
        public static final String SOURCE_LEASE_ID_HEADER = PREFIX_FOR_STORAGE_HEADER + "source-lease-id";

        /**
         * The header for data ranges.
         */
        public static final String STORAGE_RANGE_HEADER = PREFIX_FOR_STORAGE_HEADER + "range";

        /**
         * The header for storage version.
         */
        public static final String STORAGE_VERSION_HEADER = PREFIX_FOR_STORAGE_HEADER + "version";

        /**
         * The current storage version header value.
         */
        public static final String TARGET_STORAGE_VERSION = "2017-04-17";

        /**
         * The header that specifies the next visible time for a queue message.
         */
        public static final String TIME_NEXT_VISIBLE_HEADER = PREFIX_FOR_STORAGE_HEADER + "time-next-visible";

        /**
         * The UserAgent header.
         */
        public static final String USER_AGENT = "User-Agent";

        /**
         * Specifies the value to use for UserAgent header.
         */
        public static final String USER_AGENT_PREFIX = "Azure-Storage";

        /**
         * Specifies the value to use for UserAgent header.
         */
        public static final String USER_AGENT_VERSION = "2.0.0";

        /**
         * The default type for content-type and accept
         */
        public static final String XML_TYPE = "application/xml";
    }

    /**
     * Defines constants for use with query strings.
     */
    public static class QueryConstants {
        /**
         * The query component for the api version.
         */
        public static final String API_VERSION = "api-version";

        /**
         * Query component for SAS cache control.
         */
        public static final String CACHE_CONTROL = "rscc";

        /**
         * Query component for SAS content type.
         */
        public static final String CONTENT_TYPE = "rsct";

        /**
         * Query component for SAS content encoding.
         */
        public static final String CONTENT_ENCODING = "rsce";

        /**
         * Query component for SAS content language.
         */
        public static final String CONTENT_LANGUAGE = "rscl";

        /**
         * Query component for SAS content disposition.
         */
        public static final String CONTENT_DISPOSITION = "rscd";

        /**
         * Query component for the operation (component) to access.
         */
        public static final String COMPONENT = "comp";

        /**
         * Query component for copy.
         */
        public static final String COPY = "copy";

        /**
         * Query component for the copy ID.
         */
        public static final String COPY_ID = "copyid";

        /**
         * The query component for the SAS end partition key.
         */
        public static final String END_PARTITION_KEY = "epk";

        /**
         * The query component for the SAS end row key.
         */
        public static final String END_ROW_KEY = "erk";

        /**
         * Query component value for list.
         */
        public static final String LIST = "list";

        /**
         * Query component value for properties.
         */
        public static final String PROPERTIES = "properties";

        /**
         * Query component for resource type.
         */
        public static final String RESOURCETYPE = "restype";

        /**
         * The query component for the SAS table name.
         */
        public static final String SAS_TABLE_NAME = "tn";

        /**
         * The query component for the SAS signature.
         */
        public static final String SIGNATURE = "sig";

        /**
         * The query component for the signed SAS expiry time.
         */
        public static final String SIGNED_EXPIRY = "se";

        /**
         * The query component for the signed SAS identifier.
         */
        public static final String SIGNED_IDENTIFIER = "si";

        /**
         * The query component for the signed SAS IP address.
         */
        public static final String SIGNED_IP = "sip";

        /**
         * The query component for the signing SAS key.
         */
        public static final String SIGNED_KEY = "sk";

        /**
         * The query component for the signed SAS permissions.
         */
        public static final String SIGNED_PERMISSIONS = "sp";

        /**
         * The query component for the signed SAS Internet protocols.
         */
        public static final String SIGNED_PROTOCOLS = "spr";

        /**
         * The query component for the signed SAS resource.
         */
        public static final String SIGNED_RESOURCE = "sr";

        /**
         * The query component for the signed SAS resource type.
         */
        public static final String SIGNED_RESOURCE_TYPE = "srt";

        /**
         * The query component for the signed SAS service.
         */
        public static final String SIGNED_SERVICE = "ss";

        /**
         * The query component for the signed SAS start time.
         */
        public static final String SIGNED_START = "st";

        /**
         * The query component for the signed SAS version.
         */
        public static final String SIGNED_VERSION = "sv";

        /**
         * The query component for snapshot time.
         */
        public static final String SNAPSHOT = "snapshot";

        /**
         * The query component for snapshot time.
         */
        public static final String SHARE_SNAPSHOT = "sharesnapshot";

        /**
         * The query component for the SAS start partition key.
         */
        public static final String START_PARTITION_KEY = "spk";

        /**
         * The query component for the SAS start row key.
         */
        public static final String START_ROW_KEY = "srk";

        /**
         * The query component for stats.
         */
        public static final String STATS = "stats";

        /**
         * The query component for delimiter.
         */
        public static final String DELIMITER = "delimiter";

        /**
         * The query component for include.
         */
        public static final String INCLUDE = "include";

        /**
         * The query component for marker.
         */
        public static final String MARKER = "marker";

        /**
         * The query component for max results.
         */
        public static final String MAX_RESULTS = "maxresults";

        /**
         * The query component for metadata.
         */
        public static final String METADATA = "metadata";

        /**
         * The query component for prefix.
         */
        public static final String PREFIX = "prefix";

        /**
         * The query component for acl.
         */
        public static final String ACL = "acl";
    }

    /**
     * The master Microsoft Azure Storage header prefix.
     */
    public static final String PREFIX_FOR_STORAGE_HEADER = "x-ms-";

    /**
     * Constant representing a kilobyte (Non-SI version).
     */
    public static final int KB = 1024;

    /**
     * Constant representing a megabyte (Non-SI version).
     */
    public static final int MB = 1024 * KB;

    /**
     * Constant representing a gigabyte (Non-SI version).
     */
    public static final int GB = 1024 * MB;

    /**
     * XML element for an access policy.
     */
    public static final String ACCESS_POLICY = "AccessPolicy";

    /**
     * XML element for access tier.
     */
    public static final String ACCESS_TIER = "AccessTier";

    /**
     * XML element for the archive status.
     */
    public static final String ARCHIVE_STATUS = "ArchiveStatus";

    /**
     * Buffer width used to copy data to output streams.
     */
    public static final int BUFFER_COPY_LENGTH = 8 * KB;

    /**
     * XML element for the copy completion time.
     */
    public static final String COPY_COMPLETION_TIME_ELEMENT = "CopyCompletionTime";

    /**
     * XML element for the copy id.
     */
    public static final String COPY_ID_ELEMENT = "CopyId";

    /**
     * XML element for the copy progress.
     */
    public static final String COPY_PROGRESS_ELEMENT = "CopyProgress";

    /**
     * XML element for the copy source .
     */
    public static final String COPY_SOURCE_ELEMENT = "CopySource";

    /**
     * XML element for the copy status description.
     */
    public static final String COPY_STATUS_DESCRIPTION_ELEMENT = "CopyStatusDescription";

    /**
     * XML element for the copy status.
     */
    public static final String COPY_STATUS_ELEMENT = "CopyStatus";

    /**
     * XML element for the copy type.
     */
    public static final String INCREMENTAL_COPY_ELEMENT = "IncrementalCopy";

    /**
     * XML element for the snapshot ID for the last successful incremental copy.
     */
    public static final String COPY_DESTINATION_SNAPSHOT_ID_ELEMENT = "CopyDestinationSnapshot";

    /**
     * Default read timeout. 5 min * 60 seconds * 1000 ms
     */
    public static final int DEFAULT_READ_TIMEOUT = 5 * 60 * 1000;
    
    /**
     * XML element for delimiters.
     */
    public static final String DELIMITER_ELEMENT = "Delimiter";

    /**
     * Http GET method.
     */
    public static final String HTTP_GET = "GET";

    /**
     * Http PUT method.
     */
    public static final String HTTP_PUT = "PUT";

    /**
     * Http DELETE method.
     */
    public static final String HTTP_DELETE = "DELETE";

    /**
     * Http HEAD method.
     */
    public static final String HTTP_HEAD = "HEAD";

    /**
     * Http POST method.
     */
    public static final String HTTP_POST = "POST";

    /**
     * An empty <code>String</code> to use for comparison.
     */
    public static final String EMPTY_STRING = "";

    /**
     * XML element for page range end elements.
     */
    public static final String END_ELEMENT = "End";

    /**
     * XML element for error codes.
     */
    public static final String ERROR_CODE = "Code";

    /**
     * XML element for exception details.
     */
    public static final String ERROR_EXCEPTION = "ExceptionDetails";

    /**
     * XML element for exception messages.
     */
    public static final String ERROR_EXCEPTION_MESSAGE = "ExceptionMessage";

    /**
     * XML element for stack traces.
     */
    public static final String ERROR_EXCEPTION_STACK_TRACE = "StackTrace";

    /**
     * XML element for error messages.
     */
    public static final String ERROR_MESSAGE = "Message";

    /**
     * XML root element for errors.
     */
    public static final String ERROR_ROOT_ELEMENT = "Error";

    /**
     * XML element for the ETag.
     */
    public static final String ETAG_ELEMENT = "Etag";

    /**
     * XML element for the end time of an access policy.
     */
    public static final String EXPIRY = "Expiry";

    /**
     * Constant for False.
     */
    public static final String FALSE = "false";

    /**
     * Constant for bootstrap geo-replication status.
     */
    public static final String GEO_BOOTSTRAP_VALUE = "bootstrap";

    /**
     * Constant for live geo-replication status.
     */
    public static final String GEO_LIVE_VALUE = "live";

    /**
     * Constant for unavailable geo-replication status.
     */
    public static final String GEO_UNAVAILABLE_VALUE = "unavailable";

    /**
     * Specifies HTTP.
     */
    public static final String HTTP = "http";

    /**
     * Specifies HTTPS.
     */
    public static final String HTTPS = "https";

    /**
     * Specifies both HTTPS and HTTP.
     */
    public static final String HTTPS_HTTP = "https,http";

    /**
     * XML attribute for IDs.
     */
    public static final String ID = "Id";

    /**
     * XML element for an invalid metadata name.
     */
    public static final String INVALID_METADATA_NAME = "x-ms-invalid-name";

    /**
     * XML element for the last modified date.
     */
    public static final String LAST_MODIFIED_ELEMENT = "Last-Modified";

    /**
     * Lease break period max in seconds.
     */
    public static final int LEASE_BREAK_PERIOD_MAX = 60;

    /**
     * Lease break period min in seconds.
     */
    public static final int LEASE_BREAK_PERIOD_MIN = 0;

    /**
     * XML element for the lease duration.
     */
    public static final String LEASE_DURATION_ELEMENT = "LeaseDuration";

    /**
     * Lease duration max in seconds.
     */
    public static final int LEASE_DURATION_MAX = 60;

    /**
     * Lease duration min in seconds.
     */
    public static final int LEASE_DURATION_MIN = 15;
    
    /**
     * XML element for the lease state.
     */
    public static final String LEASE_STATE_ELEMENT = "LeaseState";

    /**
     * XML element for the lease status.
     */
    public static final String LEASE_STATUS_ELEMENT = "LeaseStatus";

    /**
     * Constant signaling the resource is locked.
     */
    public static final String LOCKED_VALUE = "Locked";

    /**
     * Tag that will be used for this application if logging is enabled.
     */
    public static final String LOG_TAG = "WindowsAzureStorageSDK";

    /**
     * The maximum size of a single block.
     */
    public static int MAX_BLOCK_SIZE = 4 * MB;

    /**
     * XML element for a marker.
     */
    public static final String MARKER_ELEMENT = "Marker";

    /**
     * The default write size, in bytes, used by {@link BlobOutputStream} or {@link FileOutputStream}.
     */
    public static final int DEFAULT_STREAM_WRITE_IN_BYTES = Constants.MAX_BLOCK_SIZE;

    /**
     * The default minimum read size, in bytes, for a {@link BlobInputStream} or {@link FileInputStream}.
     */
    public static final int DEFAULT_MINIMUM_READ_SIZE_IN_BYTES = Constants.MAX_BLOCK_SIZE;

    /**
     * The maximum size, in bytes, of a given stream mark operation.
     */
    // Note if BlobConstants.MAX_SINGLE_UPLOAD_BLOB_SIZE_IN_BYTES is updated then this needs to be as well.
    public static final int MAX_MARK_LENGTH = 64 * MB;

    /**
     * XML element for maximum results.
     */
    public static final String MAX_RESULTS_ELEMENT = "MaxResults";

    /**
     * Maximum number of shared access policy identifiers supported by server.
     */
    public static final int MAX_SHARED_ACCESS_POLICY_IDENTIFIERS = 5;

    /**
     * Number of default concurrent requests for parallel operation.
     */
    public static final int MAXIMUM_SEGMENTED_RESULTS = 5000;

    /**
     * XML element for the metadata.
     */
    public static final String METADATA_ELEMENT = "Metadata";

    /**
     * XML element for names.
     */
    public static final String NAME_ELEMENT = "Name";

    /**
     * XML element for the next marker.
     */
    public static final String NEXT_MARKER_ELEMENT = "NextMarker";

    /**
     * The size of a page, in bytes, in a page blob.
     */
    public static final int PAGE_SIZE = 512;

    /**
     * XML element for the permission of an access policy.
     */
    public static final String PERMISSION = "Permission";

    /**
     * XML element for a prefix.
     */
    public static final String PREFIX_ELEMENT = "Prefix";

    /**
     * XML element for properties.
     */
    public static final String PROPERTIES = "Properties";

    /**
     * XML element for  public access
     */
    public static final String PUBLIC_ACCESS_ELEMENT = "PublicAccess";

    /**
     * XML element for the server encryption status.
     */
    public static final String SERVER_ENCRYPTION_STATUS_ELEMENT = "ServerEncrypted";
    
    /**
     * XML element for a signed identifier.
     */
    public static final String SIGNED_IDENTIFIER_ELEMENT = "SignedIdentifier";

    /**
     * XML element for signed identifiers.
     */
    public static final String SIGNED_IDENTIFIERS_ELEMENT = "SignedIdentifiers";

    /**
     * XML element for the start time of an access policy.
     */
    public static final String START = "Start";

    /**
     * Constant for True.
     */
    public static final String TRUE = "true";

    /**
     * Constant signaling the resource is unlocked.
     */
    public static final String UNLOCKED_VALUE = "Unlocked";

    /**
     * Constant signaling the resource lease duration, state or status is unspecified.
     */
    public static final String UNSPECIFIED_VALUE = "Unspecified";

    /**
     * XML element for the URL.
     */
    public static final String URL_ELEMENT = "Url";

    /**
     * The default type for content-type and accept
     */
    public static final String UTF8_CHARSET = "UTF-8";

    /**
     * Private Default Ctor
     */
    private Constants() {
        // No op
    }
} public final class OperationContext {

    /**
     * The default log level, or null if disabled. The default can be overridden to turn on logging for an individual
     * operation context instance by using setLoggingEnabled.
     */
    private static Integer defaultLogLevel;

    /**
     * Indicates whether the client library should produce log entries by default. The default can be overridden to
     * enable logging for an individual operation context instance by using {@link #setLoggingEnabled}.
     */
    private static boolean enableLoggingByDefault = false;
    
    /**
     * Indicates whether the client library should use a proxy by default. The default can be overridden to
     * enable proxy for an individual operation context instance by using {@link #setProxy}.
     */
    private static Proxy proxyDefault;

    /**
     * Represents a proxy to be used when making a request.
     */
    private Proxy proxy;

    /**
     * Represents the operation latency, in milliseconds, from the client's perspective. This may include any potential
     * retries.
     */
    private long clientTimeInMs;

    /**
     * The UUID representing the client side trace ID.
     */
    private String clientRequestID;

    /**
     * The log level for a given operation context, null if disabled.
     */
    private Integer logLevel;

    /**
     * Represents request results, in the form of an <code>ArrayList</code> object that contains the
     * {@link RequestResult} objects, for each physical request that is made.
     */
    private final ArrayList<RequestResult> requestResults;

    /**
     * Represents additional headers on the request, for example, for proxy or logging information.
     */
    private HashMap<String, String> userHeaders;

    /**
     * Represents an event that is triggered before sending a
     * request.
     * 
     * @see StorageEvent
     * @see StorageEventMultiCaster
     * @see SendingRequestEvent
     */
    private static StorageEventMultiCaster<SendingRequestEvent, StorageEvent<SendingRequestEvent>> globalSendingRequestEventHandler = new StorageEventMultiCaster<SendingRequestEvent, StorageEvent<SendingRequestEvent>>();

    /**
     * Represents an event that is triggered when a response is received from the storage service while processing a request
     *
     * @see StorageEvent
     * @see StorageEventMultiCaster
     * @see ResponseReceivedEvent
     */
    private static StorageEventMultiCaster<ResponseReceivedEvent, StorageEvent<ResponseReceivedEvent>> globalResponseReceivedEventHandler = new StorageEventMultiCaster<ResponseReceivedEvent, StorageEvent<ResponseReceivedEvent>>();

    /**
     * Represents an event that is triggered when a network error occurs before the HTTP response status and headers are received.
     *
     * @see StorageEvent
     * @see StorageEventMultiCaster
     * @see ErrorReceivingResponseEvent
     */
    private static StorageEventMultiCaster<ErrorReceivingResponseEvent, StorageEvent<ErrorReceivingResponseEvent>> globalErrorReceivingResponseEventHandler = new StorageEventMultiCaster<ErrorReceivingResponseEvent, StorageEvent<ErrorReceivingResponseEvent>>();

    /**
     * Represents an event that is triggered when a response received from the service is fully processed.
     * 
     * @see StorageEvent
     * @see StorageEventMultiCaster
     * @see RequestCompletedEvent
     */
    private static StorageEventMultiCaster<RequestCompletedEvent, StorageEvent<RequestCompletedEvent>> globalRequestCompletedEventHandler = new StorageEventMultiCaster<RequestCompletedEvent, StorageEvent<RequestCompletedEvent>>();

    /**
     * Represents an event that is triggered when a request is retried.
     * 
     * @see StorageEvent
     * @see StorageEventMultiCaster
     * @see RetryingEvent
     */
    private static StorageEventMultiCaster<RetryingEvent, StorageEvent<RetryingEvent>> globalRetryingEventHandler = new StorageEventMultiCaster<RetryingEvent, StorageEvent<RetryingEvent>>();

    /**
     * Represents an event that is triggered before sending a request.
     * 
     * @see StorageEvent
     * @see StorageEventMultiCaster
     * @see SendingRequestEvent
     */
    private StorageEventMultiCaster<SendingRequestEvent, StorageEvent<SendingRequestEvent>> sendingRequestEventHandler = new StorageEventMultiCaster<SendingRequestEvent, StorageEvent<SendingRequestEvent>>();

    /**
     * Represents an event that is triggered when a response is received from the storage service while processing a
     * request.
     * 
     * @see StorageEvent
     * @see StorageEventMultiCaster
     * @see ResponseReceivedEvent
     */
    private StorageEventMultiCaster<ResponseReceivedEvent, StorageEvent<ResponseReceivedEvent>> responseReceivedEventHandler = new StorageEventMultiCaster<ResponseReceivedEvent, StorageEvent<ResponseReceivedEvent>>();

    /**
     * Represents an event that is triggered when a network error occurs before the HTTP response status and headers are received.
     *
     * @see StorageEvent
     * @see StorageEventMultiCaster
     * @see ErrorReceivingResponseEvent
     */
    private StorageEventMultiCaster<ErrorReceivingResponseEvent, StorageEvent<ErrorReceivingResponseEvent>> errorReceivingResponseEventHandler = new StorageEventMultiCaster<ErrorReceivingResponseEvent, StorageEvent<ErrorReceivingResponseEvent>>();

    /**
     * Represents an event that is triggered when a response received from the service is fully processed.
     * 
     * @see StorageEvent
     * @see StorageEventMultiCaster
     * @see RequestCompletedEvent
     */
    private StorageEventMultiCaster<RequestCompletedEvent, StorageEvent<RequestCompletedEvent>> requestCompletedEventHandler = new StorageEventMultiCaster<RequestCompletedEvent, StorageEvent<RequestCompletedEvent>>();

    /**
     * Represents an event that is triggered when a response is received from the storage service while processing a
     * request.
     * 
     * @see StorageEvent
     * @see StorageEventMultiCaster
     * @see RetryingEvent
     */
    private StorageEventMultiCaster<RetryingEvent, StorageEvent<RetryingEvent>> retryingEventHandler = new StorageEventMultiCaster<RetryingEvent, StorageEvent<RetryingEvent>>();

    /**
     * Creates an instance of the <code>OperationContext</code> class.
     */
    public OperationContext() {
        this.clientRequestID = UUID.randomUUID().toString();
        this.requestResults = new ArrayList<RequestResult>();
    }

    /**
     * Gets the client side trace ID.
     * 
     * @return A <code>String</cod> which represents the client request ID.
     */
    public String getClientRequestID() {
        return this.clientRequestID;
    }

    /**
     * Gets the operation latency, in milliseconds, from the client's perspective. This may include any potential
     * retries.
     * 
     * @return A <code>long</code> which contains the client latency time in milliseconds.
     */
    public long getClientTimeInMs() {
        return this.clientTimeInMs;
    }

    /**
     * Gets the last request result encountered for the operation.
     * 
     * @return A {@link RequestResult} object which represents the last request result.
     */
    public synchronized RequestResult getLastResult() {
        if (this.requestResults == null || this.requestResults.size() == 0) {
            return null;
        }
        else {
            return this.requestResults.get(this.requestResults.size() - 1);
        }
    }

    /**
     * Gets a proxy which will be used when making a request. Default is <code>null</code>. To set a proxy to use by 
     * default, use {@link #setDefaultProxy}
     * 
     * @return A {@link java.net.Proxy} to use when making a request.
     */
    public Proxy getProxy() {
        return this.proxy;
    }

    /**
     * Gets any additional headers for the request, for example, for proxy or logging information.
     * 
     * @return A <code>java.util.HashMap</code> which contains the the user headers for the request.
     */
    public HashMap<String, String> getUserHeaders() {
        return this.userHeaders;
    }

    /**
     * Returns the set of request results that the current operation has created.
     * 
     * @return An <code>ArrayList</code> object that contains {@link RequestResult} objects that represent
     *         the request results created by the current operation.
     */
    public ArrayList<RequestResult> getRequestResults() {
        return this.requestResults;
    }

    /**
     * Reserved for internal use. Appends a {@link RequestResult} object to the internal collection in a synchronized
     * manner.
     * 
     * @param requestResult
     *        A {@link RequestResult} to append.
     */
    public synchronized void appendRequestResult(RequestResult requestResult) {
        this.requestResults.add(requestResult);
    }

    /**
     * Gets an Integer indicating at what level the client library should produce log entries by default. The
     * default can be overridden to turn on logging for an individual operation context instance by using
     * {@link #setLogLevel(Integer)}.
     * 
     * @return the <code>android.util.Log</code> level to log at, or null if disabled
     * @see <a href="http://developer.android.com/reference/android/util/Log.html#constants">Android Log Constants</a>
     */
    public static Integer getDefaultLogLevel() {
        return defaultLogLevel;
    }

    /**
     * Gets a global event multi-caster that is triggered before sending a request. It allows event listeners to be
     * dynamically added and removed.
     * 
     * @return A {@link StorageEventMultiCaster} object for the <code>globalSendingRequestEventHandler</code>.
     */
    public static StorageEventMultiCaster<SendingRequestEvent, StorageEvent<SendingRequestEvent>> getGlobalSendingRequestEventHandler() {
        return OperationContext.globalSendingRequestEventHandler;
    }

    /**
     * Gets a global event multi-caster that is triggered when a response is received. It allows event listeners to be
     * dynamically added and removed.
     * 
     * @return A {@link StorageEventMultiCaster} object for the <code>globalResponseReceivedEventHandler</code>.
     */
    public static StorageEventMultiCaster<ResponseReceivedEvent, StorageEvent<ResponseReceivedEvent>> getGlobalResponseReceivedEventHandler() {
        return OperationContext.globalResponseReceivedEventHandler;
    }

    /**
     * Gets a global event multi-caster that is triggered when a network error occurs before the HTTP response status and headers are received.
     * It allows event listeners to be dynamically added and removed.
     *
     * @return A {@link StorageEventMultiCaster} object for the <code>globabErrorReceivingResponseEventHandler</code>.
     */
    public static StorageEventMultiCaster<ErrorReceivingResponseEvent, StorageEvent<ErrorReceivingResponseEvent>> getGlobalErrorReceivingResponseEventHandler() {
        return OperationContext.globalErrorReceivingResponseEventHandler;
    }

    /**
     * Gets a global event multi-caster that is triggered when a request is completed. It allows event listeners to be
     * dynamically added and removed.
     * 
     * @return A {@link StorageEventMultiCaster} object for the <code>globalRequestCompletedEventHandler</code>.
     */
    public static StorageEventMultiCaster<RequestCompletedEvent, StorageEvent<RequestCompletedEvent>> getGlobalRequestCompletedEventHandler() {
        return OperationContext.globalRequestCompletedEventHandler;
    }

    /**
     * Gets a global event multi-caster that is triggered when a request is retried. It allows event listeners to be
     * dynamically added and removed.
     * 
     * @return A {@link StorageEventMultiCaster} object for the <code>globalRetryingEventHandler</code>.
     */
    public static StorageEventMultiCaster<RetryingEvent, StorageEvent<RetryingEvent>> getGlobalRetryingEventHandler() {
        return OperationContext.globalRetryingEventHandler;
    }

    /**
     * Gets the log level for this operation context.
     * 
     * @return the <code>android.util.Log</code> level to log at, or null if disabled
     * @see <a href="http://developer.android.com/reference/android/util/Log.html#constants">Android Log Constants</a>
     */
    public Integer getLogLevel() {
        return this.logLevel;
    }

    /**
     * Gets an event multi-caster that is triggered before sending a request. It allows event listeners to be
     * dynamically added and removed.
     * 
     * @return A {@link StorageEventMultiCaster} object for the <code>sendingRequestEventHandler</code>.
     */
    public StorageEventMultiCaster<SendingRequestEvent, StorageEvent<SendingRequestEvent>> getSendingRequestEventHandler() {
        return this.sendingRequestEventHandler;
    }

    /**
     * Gets an event multi-caster that is triggered when a response is received. It allows event listeners to be
     * dynamically added and removed.
     * 
     * @return A {@link StorageEventMultiCaster} object for the <code>responseReceivedEventHandler</code>.
     */
    public StorageEventMultiCaster<ResponseReceivedEvent, StorageEvent<ResponseReceivedEvent>> getResponseReceivedEventHandler() {
        return this.responseReceivedEventHandler;
    }

    /**
     * Gets an event multi-caster that is triggered when a network error occurs before the HTTP response status and headers are received.
     * It allows event listeners to be dynamically added and removed.
     *
     * @return A {@link StorageEventMultiCaster} object for the <code>errorReceivingResponseEventHandler</code>.
     */
    public StorageEventMultiCaster<ErrorReceivingResponseEvent, StorageEvent<ErrorReceivingResponseEvent>> getErrorReceivingResponseEventHandler() {
        return this.errorReceivingResponseEventHandler;
    }

    /**
     * Gets an event multi-caster that is triggered when a request is completed. It allows event listeners to be
     * dynamically added and removed.
     * 
     * @return A {@link StorageEventMultiCaster} object for the <code>requestCompletedEventHandler</code>.
     */
    public StorageEventMultiCaster<RequestCompletedEvent, StorageEvent<RequestCompletedEvent>> getRequestCompletedEventHandler() {
        return this.requestCompletedEventHandler;
    }

    /**
     * Gets an event multi-caster that is triggered when a request is retried. It allows event listeners to be
     * dynamically added and removed.
     * 
     * @return A {@link StorageEventMultiCaster} object for the <code>retryingEventHandler</code>.
     */
    public StorageEventMultiCaster<RetryingEvent, StorageEvent<RetryingEvent>> getRetryingEventHandler() {
        return this.retryingEventHandler;
    }

    /**
     * Reserved for internal use. Initializes the <code>OperationContext</code> in order to begin processing a
     * new operation. All operation specific information is erased.
     */
    public void initialize() {
        this.setClientTimeInMs(0);
        this.requestResults.clear();
    }

    /**
     * Set the client side trace ID.
     * 
     * @param clientRequestID
     *            A <code>String</code> which contains the client request ID to set.
     */
    public void setClientRequestID(final String clientRequestID) {
        this.clientRequestID = clientRequestID;
    }

    /**
     * Reserved for internal use. Represents the operation latency, in milliseconds, from the client's perspective. This
     * may include any potential retries.
     * 
     * @param clientTimeInMs
     *            A <code>long</code> which contains the client operation latency in milliseconds.
     */
    public void setClientTimeInMs(final long clientTimeInMs) {
        this.clientTimeInMs = clientTimeInMs;
    }

    /**
     * Specifies an Integer indicating at what level the client library should produce log entries by default. The
     * default can be overridden to turn on logging for an individual operation context instance by using
     * {@link #setLogLevel(Integer)}.
     * 
     * @param defaultLogLevel
     *            the <code>android.util.Log</code> level to log at, or null if disabled
     * @see <a href="http://developer.android.com/reference/android/util/Log.html#constants">Android Log Constants</a>
     */
    public static void setDefaultLogLevel(Integer defaultLogLevel) {
        OperationContext.defaultLogLevel = defaultLogLevel;
    }

    /**
     * Sets a proxy which will be used when making a request. Default is <code>null</code>. To set a proxy to use by 
     * default, use {@link #setDefaultProxy}
     * 
     * @param proxy
     *            A {@link java.net.Proxy} to use when making a request.
     */
    public void setProxy(Proxy proxy) {
        this.proxy = proxy;
    }

    /**
     * Sets any additional headers for the request, for example, for proxy or logging information.
     * 
     * @param userHeaders
     *            A <code>java.util.HashMap</code> which contains any additional headers to set.
     */
    public void setUserHeaders(final HashMap<String, String> userHeaders) {
        this.userHeaders = userHeaders;
    }

    /**
     * Sets a global event multi-caster that is triggered before sending a request.
     * 
     * @param globalSendingRequestEventHandler
     *            The {@link StorageEventMultiCaster} object to set for the
     *            <code>globalSendingRequestEventHandler</code>.
     */
    public static void setGlobalSendingRequestEventHandler(
            final StorageEventMultiCaster<SendingRequestEvent, StorageEvent<SendingRequestEvent>> globalSendingRequestEventHandler) {
        OperationContext.globalSendingRequestEventHandler = globalSendingRequestEventHandler;
    }

    /**
     * Sets a global event multi-caster that is triggered when a response is received.
     * 
     * @param globalResponseReceivedEventHandler
     *            The {@link StorageEventMultiCaster} object to set for the
     *            <code>globalResponseReceivedEventHandler</code>.
     */
    public static void setGlobalResponseReceivedEventHandler(
            final StorageEventMultiCaster<ResponseReceivedEvent, StorageEvent<ResponseReceivedEvent>> globalResponseReceivedEventHandler) {
        OperationContext.globalResponseReceivedEventHandler = globalResponseReceivedEventHandler;
    }

    /**
     * Sets a global event multi-caster that is triggered when a network error occurs before the HTTP response status and headers are received.
     *
     * @param globalErrorReceivingResponseEventHandler
     *        The {@link StorageEventMultiCaster} object to set for the <code>globalErrorReceivingResponseEventHandler</code>.
     */
    public static void setGlobalErrorReceivingResponseEventHandler(
            final StorageEventMultiCaster<ErrorReceivingResponseEvent, StorageEvent<ErrorReceivingResponseEvent>> globalErrorReceivingResponseEventHandler) {
        OperationContext.globalErrorReceivingResponseEventHandler = globalErrorReceivingResponseEventHandler;
    }

    /**
     * Sets a global event multi-caster that is triggered when a request is completed.
     * 
     * @param globalRequestCompletedEventHandler
     *            The {@link StorageEventMultiCaster} object to set for the
     *            <code>globalRequestCompletedEventHandler</code>.
     */
    public static void setGlobalRequestCompletedEventHandler(
            final StorageEventMultiCaster<RequestCompletedEvent, StorageEvent<RequestCompletedEvent>> globalRequestCompletedEventHandler) {
        OperationContext.globalRequestCompletedEventHandler = globalRequestCompletedEventHandler;
    }

    /**
     * Sets a global event multi-caster that is triggered when a request is retried.
     * 
     * @param globalRetryingEventHandler
     *            The {@link StorageEventMultiCaster} object to set for the <code>globalRetryingEventHandler</code>.
     */
    public static void setGlobalRetryingEventHandler(
            final StorageEventMultiCaster<RetryingEvent, StorageEvent<RetryingEvent>> globalRetryingEventHandler) {
        OperationContext.globalRetryingEventHandler = globalRetryingEventHandler;
    }

    /**
     * Sets the log level for this operation context.
     * 
     * @param logLevel
     *            the <code>android.util.Log</code> level to log at, or null to disable
     * @see <a href="http://developer.android.com/reference/android/util/Log.html#constants">Android Log Constants</a>
     */
    public void setLogLevel(Integer logLevel) {
        this.logLevel = logLevel;
    }

    /**
     * Sets an event multi-caster that is triggered before sending a request.
     * 
     * @param sendingRequestEventHandler
     *            The {@link StorageEventMultiCaster} object to set for the <code>sendingRequestEventHandler</code>.
     */
    public void setSendingRequestEventHandler(
            final StorageEventMultiCaster<SendingRequestEvent, StorageEvent<SendingRequestEvent>> sendingRequestEventHandler) {
        this.sendingRequestEventHandler = sendingRequestEventHandler;
    }

    /**
     * Sets an event multi-caster that is triggered when a response is received.
     * 
     * @param responseReceivedEventHandler
     *            The {@link StorageEventMultiCaster} object to set for the <code>responseReceivedEventHandler</code>.
     */
    public void setResponseReceivedEventHandler(
            final StorageEventMultiCaster<ResponseReceivedEvent, StorageEvent<ResponseReceivedEvent>> responseReceivedEventHandler) {
        this.responseReceivedEventHandler = responseReceivedEventHandler;
    }

    /**
     * Sets an event multi-caster that is triggered when a network error occurs before the HTTP response status and headers are received.
     *
     * @param errorReceivingResponseEventHandler
     *        The {@link StorageEventMultiCaster} object to set for the <code>errorReceivingResponseEventHandler</code>.
     */
    public void setErrorReceivingResponseEventHandler(
            final StorageEventMultiCaster<ErrorReceivingResponseEvent, StorageEvent<ErrorReceivingResponseEvent>> errorReceivingResponseEventHandler) {
        this.errorReceivingResponseEventHandler = errorReceivingResponseEventHandler;
    }

    /**
     * Sets an event multi-caster that is triggered when a request is completed.
     * 
     * @param requestCompletedEventHandler
     *            The {@link StorageEventMultiCaster} object to set for the <code>requestCompletedEventHandler</code>.
     */
    public void setRequestCompletedEventHandler(
            final StorageEventMultiCaster<RequestCompletedEvent, StorageEvent<RequestCompletedEvent>> requestCompletedEventHandler) {
        this.requestCompletedEventHandler = requestCompletedEventHandler;
    }

    /**
     * Sets an event multi-caster that is triggered when a request is retried.
     * 
     * @param retryingEventHandler
     *            The {@link StorageEventMultiCaster} object to set for the <code>retryingEventHandler</code>.
     */
    public void setRetryingEventHandler(
            final StorageEventMultiCaster<RetryingEvent, StorageEvent<RetryingEvent>> retryingEventHandler) {
        this.retryingEventHandler = retryingEventHandler;
    }

    /**
     * Gets the default proxy used by the client library if enabled. The default can be overridden
     * to enable a proxy for an individual operation context instance by using {@link #setProxy}.
     * 
     * @return The default {@link java.net.Proxy} if set; otherwise <code>null</code>
     */
    public static Proxy getDefaultProxy() {
        return OperationContext.proxyDefault;
    }

    /**
     * Specifies the proxy the client library should use by default. The default can be overridden
     * to turn on a proxy for an individual operation context instance by using {@link #setProxy}.
     * 
     * @param defaultProxy
     *        The {@link java.net.Proxy} to use by default, or <code>null</code> to not use a proxy.
     */
    public static void setDefaultProxy(Proxy defaultProxy) {
        OperationContext.proxyDefault = defaultProxy;
    }
} @Category({ SecondaryTests.class, DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class SecondaryTests {

    @Test
    public void testLocationModeWithMissingUri() throws URISyntaxException, StorageException {
        CloudBlobClient client = TestHelper.createCloudBlobClient();
        CloudBlobClient primaryOnlyClient = new CloudBlobClient(client.getEndpoint(), client.getCredentials());
        CloudBlobContainer container = primaryOnlyClient.getContainerReference("nonexistingcontainer");

        BlobRequestOptions options = new BlobRequestOptions();
        options.setLocationMode(LocationMode.SECONDARY_ONLY);
        options.setRetryPolicyFactory(new RetryNoRetry());

        try {
            container.downloadAttributes(null, options, null);
            fail();
        }
        catch (StorageException ex) {
            assertEquals(UnsupportedOperationException.class, ex.getCause().getClass());
        }

        options.setLocationMode(LocationMode.SECONDARY_THEN_PRIMARY);

        try {
            container.downloadAttributes(null, options, null);
            fail();
        }
        catch (StorageException ex) {
            assertEquals(UnsupportedOperationException.class, ex.getCause().getClass());
        }

        options.setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY);

        try {
            container.downloadAttributes(null, options, null);
            fail();
        }
        catch (StorageException ex) {
            assertEquals(UnsupportedOperationException.class, ex.getCause().getClass());
        }
    }

    @Test
    public void testBlobIfExistsShouldNotHitSecondary() throws StorageException, URISyntaxException {
        CloudBlobContainer container = BlobTestHelper.getRandomContainerReference();

        BlobRequestOptions options = new BlobRequestOptions();

        // CreateIfNotExists
        OperationContext context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_ONLY);
        container.createIfNotExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY);
        container.createIfNotExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_THEN_PRIMARY);
        container.createIfNotExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_ONLY);
        try {
            container.createIfNotExists(options, context);
        }
        catch (StorageException ex) {
            assertEquals(IllegalArgumentException.class, ex.getCause().getClass());
        }

        // DeleteIfExists
        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_ONLY);
        container.deleteIfExists(null, options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY);
        container.deleteIfExists(null, options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_THEN_PRIMARY);
        container.deleteIfExists(null, options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_ONLY);
        try {
            container.deleteIfExists(null, options, context);
        }
        catch (StorageException ex) {
            assertEquals(IllegalArgumentException.class, ex.getCause().getClass());
        }

        CloudBlockBlob blockBlob = container.getBlockBlobReference("blob1");

        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_ONLY);
        blockBlob.deleteIfExists(DeleteSnapshotsOption.NONE, null, options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY);
        blockBlob.deleteIfExists(DeleteSnapshotsOption.NONE, null, options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_THEN_PRIMARY);
        blockBlob.deleteIfExists(DeleteSnapshotsOption.NONE, null, options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_ONLY);
        try {
            blockBlob.deleteIfExists(DeleteSnapshotsOption.NONE, null, options, context);
        }
        catch (StorageException ex) {
            assertEquals(IllegalArgumentException.class, ex.getCause().getClass());
        }

        CloudPageBlob pageBlob = container.getPageBlobReference("blob2");

        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_ONLY);
        pageBlob.deleteIfExists(DeleteSnapshotsOption.NONE, null, options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY);
        pageBlob.deleteIfExists(DeleteSnapshotsOption.NONE, null, options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_THEN_PRIMARY);
        pageBlob.deleteIfExists(DeleteSnapshotsOption.NONE, null, options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_ONLY);
        try {
            pageBlob.deleteIfExists(DeleteSnapshotsOption.NONE, null, options, context);
        }
        catch (StorageException ex) {
            assertEquals(IllegalArgumentException.class, ex.getCause().getClass());
        }
    }

    @Test
    public void testQueueIfExistsShouldNotHitSecondary() throws StorageException, URISyntaxException {

        CloudQueueClient client = TestHelper.createCloudQueueClient();

        CloudQueue queue = client.getQueueReference("nonexistantqueue");

        QueueRequestOptions options = new QueueRequestOptions();

        // CreateIfNotExists
        OperationContext context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_ONLY);
        queue.createIfNotExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY);
        queue.createIfNotExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_THEN_PRIMARY);
        queue.createIfNotExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_ONLY);
        try {
            queue.createIfNotExists(options, context);
        }
        catch (StorageException ex) {
            assertEquals(IllegalArgumentException.class, ex.getCause().getClass());
        }

        // DeleteIfExists
        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_ONLY);
        queue.deleteIfExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY);
        queue.deleteIfExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_THEN_PRIMARY);
        queue.deleteIfExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_ONLY);
        try {
            queue.deleteIfExists(options, context);
        }
        catch (StorageException ex) {
            assertEquals(IllegalArgumentException.class, ex.getCause().getClass());
        }
    }

    @Test
    public void testTableIfExistsShouldNotHitSecondary() throws StorageException, URISyntaxException {
        CloudTableClient client = TestHelper.createCloudTableClient();
        CloudTable table = client.getTableReference("nonexistanttable");

        TableRequestOptions options = new TableRequestOptions();

        // CreateIfNotExists
        OperationContext context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_ONLY);
        table.createIfNotExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY);
        table.createIfNotExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_THEN_PRIMARY);
        table.createIfNotExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_ONLY);
        try {
            table.createIfNotExists(options, context);
        }
        catch (StorageException ex) {
            assertEquals(IllegalArgumentException.class, ex.getCause().getClass());
        }

        // DeleteIfExists
        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_ONLY);
        table.deleteIfExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY);
        table.deleteIfExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_THEN_PRIMARY);
        table.deleteIfExists(options, context);
        assertEquals(StorageLocation.PRIMARY, context.getRequestResults().get(0).getTargetLocation());

        context = new OperationContext();
        options.setLocationMode(LocationMode.SECONDARY_ONLY);
        try {
            table.deleteIfExists(options, context);
        }
        catch (StorageException ex) {
            assertEquals(IllegalArgumentException.class, ex.getCause().getClass());
        }
    }

    @Test
    @Category(SlowTests.class)
    public void testMultiLocationRetriesBlob() throws URISyntaxException, StorageException {
        List<RetryInfo> retryInfoList = new ArrayList<RetryInfo>();
        List<RetryContext> retryContextList = new ArrayList<RetryContext>();

        retryContextList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));

        // Next location is set to primary and location mode is primary only.
        testContainerDownloadAttributes(LocationMode.PRIMARY_ONLY, LocationMode.PRIMARY_ONLY, StorageLocation.PRIMARY,
                retryContextList, retryInfoList);
        testContainerDownloadAttributes(null, LocationMode.PRIMARY_ONLY, StorageLocation.PRIMARY, retryContextList,
                retryInfoList);

        retryContextList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));

        // Next location is set to secondary and location mode is secondary only.
        testContainerDownloadAttributes(LocationMode.SECONDARY_ONLY, LocationMode.PRIMARY_ONLY,
                StorageLocation.SECONDARY, retryContextList, retryInfoList);
        testContainerDownloadAttributes(null, LocationMode.SECONDARY_ONLY, StorageLocation.SECONDARY, retryContextList,
                retryInfoList);

        retryContextList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.PRIMARY_THEN_SECONDARY));

        // Next location is set to secondary and location mode is PRIMARY_THEN_SECONDARY.
        testContainerDownloadAttributes(LocationMode.PRIMARY_THEN_SECONDARY, LocationMode.PRIMARY_ONLY,
                StorageLocation.PRIMARY, retryContextList, retryInfoList);
        testContainerDownloadAttributes(null, LocationMode.PRIMARY_THEN_SECONDARY, StorageLocation.PRIMARY,
                retryContextList, retryInfoList);

        retryContextList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.SECONDARY_THEN_PRIMARY));

        // Next location is set to primary and location mode is SECONDARY_THEN_PRIMARY.
        testContainerDownloadAttributes(LocationMode.SECONDARY_THEN_PRIMARY, LocationMode.PRIMARY_ONLY,
                StorageLocation.SECONDARY, retryContextList, retryInfoList);
        testContainerDownloadAttributes(null, LocationMode.SECONDARY_THEN_PRIMARY, StorageLocation.SECONDARY,
                retryContextList, retryInfoList);

        retryContextList.clear();
        retryInfoList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY, 6000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY, 1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));
        AddUpdatedLocationModes(retryContextList, retryInfoList);

        // Use AlwaysRetry and retry the request multiple times based on the retry context and retry info lists created above.
        testContainerDownloadAttributes(LocationMode.PRIMARY_ONLY, LocationMode.PRIMARY_ONLY, StorageLocation.PRIMARY,
                retryContextList, retryInfoList);
        testContainerDownloadAttributes(null, LocationMode.PRIMARY_ONLY, StorageLocation.PRIMARY, retryContextList,
                retryInfoList);

        retryContextList.clear();
        retryInfoList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY,
                6000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY,
                1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));
        AddUpdatedLocationModes(retryContextList, retryInfoList);

        // Use AlwaysRetry and retry the request multiple times based on the retry context and retry info lists created above.
        testContainerDownloadAttributes(LocationMode.SECONDARY_ONLY, LocationMode.PRIMARY_ONLY,
                StorageLocation.SECONDARY, retryContextList, retryInfoList);
        testContainerDownloadAttributes(null, LocationMode.SECONDARY_ONLY, StorageLocation.SECONDARY, retryContextList,
                retryInfoList);

        retryContextList.clear();
        retryInfoList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.PRIMARY_THEN_SECONDARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY,
                LocationMode.PRIMARY_THEN_SECONDARY, 6000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_THEN_SECONDARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY,
                LocationMode.PRIMARY_THEN_SECONDARY, 1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.PRIMARY_THEN_SECONDARY));
        AddUpdatedLocationModes(retryContextList, retryInfoList);

        // Use AlwaysRetry and retry the request multiple times based on the retry context and retry info lists created above.
        testContainerDownloadAttributes(LocationMode.PRIMARY_THEN_SECONDARY, LocationMode.PRIMARY_ONLY,
                StorageLocation.PRIMARY, retryContextList, retryInfoList);
        testContainerDownloadAttributes(null, LocationMode.PRIMARY_THEN_SECONDARY, StorageLocation.PRIMARY,
                retryContextList, retryInfoList);

        retryContextList.clear();
        retryInfoList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.SECONDARY_THEN_PRIMARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY,
                LocationMode.SECONDARY_THEN_PRIMARY, 6000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_THEN_PRIMARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY,
                LocationMode.SECONDARY_THEN_PRIMARY, 1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.SECONDARY_THEN_PRIMARY));
        AddUpdatedLocationModes(retryContextList, retryInfoList);

        // Use AlwaysRetry and retry the request multiple times based on the retry context and retry info lists created above.
        testContainerDownloadAttributes(LocationMode.SECONDARY_THEN_PRIMARY, LocationMode.PRIMARY_ONLY,
                StorageLocation.SECONDARY, retryContextList, retryInfoList);
        testContainerDownloadAttributes(null, LocationMode.SECONDARY_THEN_PRIMARY, StorageLocation.SECONDARY,
                retryContextList, retryInfoList);
    }

    @Test
    @Category(SlowTests.class)
    public void testMultiLocationRetriesQueue() throws URISyntaxException, StorageException {
        List<RetryInfo> retryInfoList = new ArrayList<RetryInfo>();
        List<RetryContext> retryContextList = new ArrayList<RetryContext>();

        retryContextList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));

        // Next location is set to primary and location mode is primary only.
        testQueueDownloadAttributes(LocationMode.PRIMARY_ONLY, LocationMode.PRIMARY_ONLY, StorageLocation.PRIMARY,
                retryContextList, retryInfoList);
        testQueueDownloadAttributes(null, LocationMode.PRIMARY_ONLY, StorageLocation.PRIMARY, retryContextList,
                retryInfoList);

        retryContextList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));

        // Next location is set to secondary and location mode is secondary only.
        testQueueDownloadAttributes(LocationMode.SECONDARY_ONLY, LocationMode.PRIMARY_ONLY, StorageLocation.SECONDARY,
                retryContextList, retryInfoList);
        testQueueDownloadAttributes(null, LocationMode.SECONDARY_ONLY, StorageLocation.SECONDARY, retryContextList,
                retryInfoList);

        retryContextList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.PRIMARY_THEN_SECONDARY));

        // Next location is set to secondary and location mode is PRIMARY_THEN_SECONDARY.
        testQueueDownloadAttributes(LocationMode.PRIMARY_THEN_SECONDARY, LocationMode.PRIMARY_ONLY,
                StorageLocation.PRIMARY, retryContextList, retryInfoList);
        testQueueDownloadAttributes(null, LocationMode.PRIMARY_THEN_SECONDARY, StorageLocation.PRIMARY,
                retryContextList, retryInfoList);

        retryContextList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.SECONDARY_THEN_PRIMARY));

        // Next location is set to primary and location mode is SECONDARY_THEN_PRIMARY.
        testQueueDownloadAttributes(LocationMode.SECONDARY_THEN_PRIMARY, LocationMode.PRIMARY_ONLY,
                StorageLocation.SECONDARY, retryContextList, retryInfoList);
        testQueueDownloadAttributes(null, LocationMode.SECONDARY_THEN_PRIMARY, StorageLocation.SECONDARY,
                retryContextList, retryInfoList);

        retryContextList.clear();
        retryInfoList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY, 6000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY, 1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));
        AddUpdatedLocationModes(retryContextList, retryInfoList);

        // Use AlwaysRetry and retry the request multiple times based on the retry context and retry info lists created above.
        testQueueDownloadAttributes(LocationMode.PRIMARY_ONLY, LocationMode.PRIMARY_ONLY, StorageLocation.PRIMARY,
                retryContextList, retryInfoList);
        testQueueDownloadAttributes(null, LocationMode.PRIMARY_ONLY, StorageLocation.PRIMARY, retryContextList,
                retryInfoList);

        retryContextList.clear();
        retryInfoList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY,
                6000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY,
                1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));
        AddUpdatedLocationModes(retryContextList, retryInfoList);

        // Use AlwaysRetry and retry the request multiple times based on the retry context and retry info lists created above.
        testQueueDownloadAttributes(LocationMode.SECONDARY_ONLY, LocationMode.PRIMARY_ONLY, StorageLocation.SECONDARY,
                retryContextList, retryInfoList);
        testQueueDownloadAttributes(null, LocationMode.SECONDARY_ONLY, StorageLocation.SECONDARY, retryContextList,
                retryInfoList);

        retryContextList.clear();
        retryInfoList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.PRIMARY_THEN_SECONDARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY,
                LocationMode.PRIMARY_THEN_SECONDARY, 6000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_THEN_SECONDARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY,
                LocationMode.PRIMARY_THEN_SECONDARY, 1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.PRIMARY_THEN_SECONDARY));
        AddUpdatedLocationModes(retryContextList, retryInfoList);

        // Use AlwaysRetry and retry the request multiple times based on the retry context and retry info lists created above.
        testQueueDownloadAttributes(LocationMode.PRIMARY_THEN_SECONDARY, LocationMode.PRIMARY_ONLY,
                StorageLocation.PRIMARY, retryContextList, retryInfoList);
        testQueueDownloadAttributes(null, LocationMode.PRIMARY_THEN_SECONDARY, StorageLocation.PRIMARY,
                retryContextList, retryInfoList);

        retryContextList.clear();
        retryInfoList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.SECONDARY_THEN_PRIMARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY,
                LocationMode.SECONDARY_THEN_PRIMARY, 6000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_THEN_PRIMARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY,
                LocationMode.SECONDARY_THEN_PRIMARY, 1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.SECONDARY_THEN_PRIMARY));
        AddUpdatedLocationModes(retryContextList, retryInfoList);

        // Use AlwaysRetry and retry the request multiple times based on the retry context and retry info lists created above.
        testQueueDownloadAttributes(LocationMode.SECONDARY_THEN_PRIMARY, LocationMode.PRIMARY_ONLY,
                StorageLocation.SECONDARY, retryContextList, retryInfoList);
        testQueueDownloadAttributes(null, LocationMode.SECONDARY_THEN_PRIMARY, StorageLocation.SECONDARY,
                retryContextList, retryInfoList);
    }

    @Test
    @Category(SlowTests.class)
    public void testMultiLocationRetriesTable() throws URISyntaxException, StorageException {
        List<RetryInfo> retryInfoList = new ArrayList<RetryInfo>();
        List<RetryContext> retryContextList = new ArrayList<RetryContext>();

        retryContextList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));

        // Next location is set to primary and location mode is primary only.
        testTableDownloadPermissions(LocationMode.PRIMARY_ONLY, LocationMode.PRIMARY_ONLY, StorageLocation.PRIMARY,
                retryContextList, retryInfoList);
        testTableDownloadPermissions(null, LocationMode.PRIMARY_ONLY, StorageLocation.PRIMARY, retryContextList,
                retryInfoList);

        retryContextList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));

        // Next location is set to secondary and location mode is secondary only.
        testTableDownloadPermissions(LocationMode.SECONDARY_ONLY, LocationMode.PRIMARY_ONLY, StorageLocation.SECONDARY,
                retryContextList, retryInfoList);
        testTableDownloadPermissions(null, LocationMode.SECONDARY_ONLY, StorageLocation.SECONDARY, retryContextList,
                retryInfoList);

        retryContextList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.PRIMARY_THEN_SECONDARY));

        // Next location is set to secondary and location mode is PRIMARY_THEN_SECONDARY.
        testTableDownloadPermissions(LocationMode.PRIMARY_THEN_SECONDARY, LocationMode.PRIMARY_ONLY,
                StorageLocation.PRIMARY, retryContextList, retryInfoList);
        testTableDownloadPermissions(null, LocationMode.PRIMARY_THEN_SECONDARY, StorageLocation.PRIMARY,
                retryContextList, retryInfoList);

        retryContextList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.SECONDARY_THEN_PRIMARY));

        // Next location is set to primary and location mode is SECONDARY_THEN_PRIMARY.
        testTableDownloadPermissions(LocationMode.SECONDARY_THEN_PRIMARY, LocationMode.PRIMARY_ONLY,
                StorageLocation.SECONDARY, retryContextList, retryInfoList);
        testTableDownloadPermissions(null, LocationMode.SECONDARY_THEN_PRIMARY, StorageLocation.SECONDARY,
                retryContextList, retryInfoList);

        retryContextList.clear();
        retryInfoList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY, 6000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY, 1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));
        AddUpdatedLocationModes(retryContextList, retryInfoList);

        // Use AlwaysRetry and retry the request multiple times based on the retry context and retry info lists created above.
        testTableDownloadPermissions(LocationMode.PRIMARY_ONLY, LocationMode.PRIMARY_ONLY, StorageLocation.PRIMARY,
                retryContextList, retryInfoList);
        testTableDownloadPermissions(null, LocationMode.PRIMARY_ONLY, StorageLocation.PRIMARY, retryContextList,
                retryInfoList);

        retryContextList.clear();
        retryInfoList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY,
                6000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY,
                1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));
        AddUpdatedLocationModes(retryContextList, retryInfoList);

        // Use AlwaysRetry and retry the request multiple times based on the retry context and retry info lists created above.
        testTableDownloadPermissions(LocationMode.SECONDARY_ONLY, LocationMode.PRIMARY_ONLY, StorageLocation.SECONDARY,
                retryContextList, retryInfoList);
        testTableDownloadPermissions(null, LocationMode.SECONDARY_ONLY, StorageLocation.SECONDARY, retryContextList,
                retryInfoList);

        retryContextList.clear();
        retryInfoList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.PRIMARY_THEN_SECONDARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY,
                LocationMode.PRIMARY_THEN_SECONDARY, 6000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_THEN_SECONDARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY,
                LocationMode.PRIMARY_THEN_SECONDARY, 1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.PRIMARY_THEN_SECONDARY));
        AddUpdatedLocationModes(retryContextList, retryInfoList);

        // Use AlwaysRetry and retry the request multiple times based on the retry context and retry info lists created above.
        testTableDownloadPermissions(LocationMode.PRIMARY_THEN_SECONDARY, LocationMode.PRIMARY_ONLY,
                StorageLocation.PRIMARY, retryContextList, retryInfoList);
        testTableDownloadPermissions(null, LocationMode.PRIMARY_THEN_SECONDARY, StorageLocation.PRIMARY,
                retryContextList, retryInfoList);

        retryContextList.clear();
        retryInfoList.clear();
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.SECONDARY_THEN_PRIMARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY,
                LocationMode.SECONDARY_THEN_PRIMARY, 6000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_THEN_PRIMARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY,
                LocationMode.SECONDARY_THEN_PRIMARY, 1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.SECONDARY_THEN_PRIMARY));
        AddUpdatedLocationModes(retryContextList, retryInfoList);

        // Use AlwaysRetry and retry the request multiple times based on the retry context and retry info lists created above.
        testTableDownloadPermissions(LocationMode.SECONDARY_THEN_PRIMARY, LocationMode.PRIMARY_ONLY,
                StorageLocation.SECONDARY, retryContextList, retryInfoList);
        testTableDownloadPermissions(null, LocationMode.SECONDARY_THEN_PRIMARY, StorageLocation.SECONDARY,
                retryContextList, retryInfoList);
    }
    
    @Test
    public void testRetryOn304() throws StorageException, IOException, URISyntaxException {
        OperationContext operationContext = new OperationContext();
        operationContext.getRetryingEventHandler().addListener(new StorageEvent<RetryingEvent>() {
            @Override
            public void eventOccurred(RetryingEvent eventArg) {
                fail("Request should not be retried.");
            }
        });

        CloudBlobContainer container = BlobTestHelper.getRandomContainerReference();
        try {
            container.create();
            CloudBlockBlob blockBlobRef = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(container, BlobType.BLOCK_BLOB,
                    "originalBlob", 1024, null);
            AccessCondition accessCondition = AccessCondition.generateIfNoneMatchCondition(blockBlobRef.getProperties().getEtag());
            blockBlobRef.download(new ByteArrayOutputStream(), accessCondition, null, operationContext);
            
            fail("Download should fail with a 304.");
        } catch (StorageException ex) {
            assertEquals("The condition specified using HTTP conditional header(s) is not met.", ex.getMessage());
        } finally {
            container.deleteIfExists();
        }
    }

    private static void AddUpdatedLocationModes(List<RetryContext> retryContextList, List<RetryInfo> retryInfoList) {
        retryInfoList
                .add(populateRetryInfo(new RetryInfo(), StorageLocation.PRIMARY, LocationMode.SECONDARY_ONLY, 4000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.SECONDARY, LocationMode.SECONDARY_ONLY));
        retryInfoList
                .add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY, LocationMode.PRIMARY_ONLY, 1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY,
                LocationMode.PRIMARY_THEN_SECONDARY, 1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.PRIMARY_THEN_SECONDARY));
        retryInfoList.add(populateRetryInfo(new RetryInfo(), StorageLocation.SECONDARY,
                LocationMode.SECONDARY_THEN_PRIMARY, 1000));
        retryContextList.add(new RetryContext(0, null, StorageLocation.PRIMARY, LocationMode.SECONDARY_THEN_PRIMARY));
    }

    private static RetryInfo populateRetryInfo(RetryInfo retryInfo, StorageLocation location,
            LocationMode locationMode, int interval) {
        retryInfo.setTargetLocation(location);
        retryInfo.setUpdatedLocationMode(locationMode);
        retryInfo.setRetryInterval(interval);
        return retryInfo;
    }

    private static void testContainerDownloadAttributes(LocationMode optionsLocationMode,
            LocationMode clientLocationMode, StorageLocation initialLocation, List<RetryContext> retryContextList,
            List<RetryInfo> retryInfoList) throws URISyntaxException, StorageException {
        CloudBlobContainer container = BlobTestHelper.getRandomContainerReference();
        MultiLocationTestHelper helper = new MultiLocationTestHelper(container.getServiceClient().getStorageUri(),
                initialLocation, retryContextList, retryInfoList);

        container.getServiceClient().getDefaultRequestOptions().setLocationMode(clientLocationMode);
        BlobRequestOptions options = new BlobRequestOptions();

        options.setLocationMode(optionsLocationMode);
        options.setRetryPolicyFactory(helper.retryPolicy);

        try {
            container.downloadAttributes(null, options, helper.operationContext);
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode());
        }
        finally {
            helper.close();
        }
    }

    private static void testQueueDownloadAttributes(LocationMode optionsLocationMode, LocationMode clientLocationMode,
            StorageLocation initialLocation, List<RetryContext> retryContextList, List<RetryInfo> retryInfoList)
            throws URISyntaxException, StorageException {
        CloudQueueClient client = TestHelper.createCloudQueueClient();
        CloudQueue queue = client.getQueueReference(QueueTestHelper.generateRandomQueueName());

        MultiLocationTestHelper helper = new MultiLocationTestHelper(queue.getServiceClient().getStorageUri(),
                initialLocation, retryContextList, retryInfoList);

        queue.getServiceClient().getDefaultRequestOptions().setLocationMode(clientLocationMode);
        QueueRequestOptions options = new QueueRequestOptions();

        options.setLocationMode(optionsLocationMode);
        options.setRetryPolicyFactory(helper.retryPolicy);

        try {
            queue.downloadAttributes(options, helper.operationContext);
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode());
        }
        finally {
            helper.close();
        }
    }

    private static void testTableDownloadPermissions(LocationMode optionsLocationMode, LocationMode clientLocationMode,
            StorageLocation initialLocation, List<RetryContext> retryContextList, List<RetryInfo> retryInfoList)
            throws URISyntaxException, StorageException {
        CloudTableClient client = TestHelper.createCloudTableClient();
        CloudTable table = client.getTableReference(TableTestHelper.generateRandomTableName());

        MultiLocationTestHelper helper = new MultiLocationTestHelper(table.getServiceClient().getStorageUri(),
                initialLocation, retryContextList, retryInfoList);

        table.getServiceClient().getDefaultRequestOptions().setLocationMode(clientLocationMode);
        TableRequestOptions options = new TableRequestOptions();

        options.setLocationMode(optionsLocationMode);
        options.setRetryPolicyFactory(helper.retryPolicy);

        try {
            table.downloadPermissions(options, helper.operationContext);
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode());
        }
        finally {
            helper.close();
        }
    }
} public class TestHelper {
	private static CloudStorageAccount account;
    private static CloudStorageAccount premiumAccount;

	public static String connectionString;
    public static String premiumConnectionString;
	public static String accountName;
    public static String premiumAccountName;
	public static String accountKey;
    public static String premiumAccountKey;
	public static StorageUri blobEndpoint;
    public static StorageUri premiumBlobEndpoint;
	public static StorageUri queueEndpoint;
	public static StorageUri tableEndpoint;

	public static CloudBlobClient createCloudBlobClient() throws StorageException {
        return getAccount().createCloudBlobClient();
    }
	
	public static CloudFileClient createCloudFileClient() throws StorageException {
		return getAccount().createCloudFileClient();
    }

	public static CloudQueueClient createCloudQueueClient() throws StorageException {
		return getAccount().createCloudQueueClient();
	}

	public static CloudTableClient createCloudTableClient() throws StorageException {
		return getAccount().createCloudTableClient();
	}

    public static CloudBlobClient createPremiumCloudBlobClient() throws StorageException {
        CloudBlobClient client = getPremiumBlobAccount().createCloudBlobClient();
        return client;
    }

    public static CloudBlobClient createCloudBlobClient(SharedAccessAccountPolicy policy, boolean useHttps)
            throws StorageException, InvalidKeyException, URISyntaxException {
        
        CloudStorageAccount sasAccount = getAccount();
        final String token = sasAccount.generateSharedAccessSignature(policy);
        final StorageCredentials creds =
                new StorageCredentialsSharedAccessSignature(token);
        
        final URI blobUri = new URI(useHttps ? "https" : "http", sasAccount.getBlobEndpoint().getAuthority(), 
        		sasAccount.getBlobEndpoint().getPath(), sasAccount.getBlobEndpoint().getQuery(), null);

        sasAccount = new CloudStorageAccount(creds, blobUri, sasAccount.getQueueEndpoint(), sasAccount.getTableEndpoint(), 
        		sasAccount.getFileEndpoint());
        return sasAccount.createCloudBlobClient();
    }

    public static CloudFileClient createCloudFileClient(SharedAccessAccountPolicy policy, boolean useHttps)
            throws StorageException, InvalidKeyException, URISyntaxException {

        CloudStorageAccount sasAccount = getAccount();
        final String token = sasAccount.generateSharedAccessSignature(policy);
        final StorageCredentials creds =
                new StorageCredentialsSharedAccessSignature(token);
        
        final URI fileUri = new URI(useHttps ? "https" : "http", sasAccount.getFileEndpoint().getAuthority(), 
        		sasAccount.getFileEndpoint().getPath(), sasAccount.getFileEndpoint().getQuery(), null);
        
        sasAccount = new CloudStorageAccount(
                creds, sasAccount.getBlobEndpoint(), sasAccount.getQueueEndpoint(), sasAccount.getTableEndpoint(),
                fileUri);
        return sasAccount.createCloudFileClient();
    }

    public static CloudQueueClient createCloudQueueClient(SharedAccessAccountPolicy policy, boolean useHttps)
            throws StorageException, InvalidKeyException, URISyntaxException {

        CloudStorageAccount sasAccount = getAccount();
        final String token = sasAccount.generateSharedAccessSignature(policy);
        final StorageCredentials creds =
                new StorageCredentialsSharedAccessSignature(token);
        
        final URI queueUri = new URI(useHttps ? "https" : "http", sasAccount.getQueueEndpoint().getAuthority(), 
        		sasAccount.getQueueEndpoint().getPath(), sasAccount.getQueueEndpoint().getQuery(), null);
        
        sasAccount = new CloudStorageAccount(creds, sasAccount.getBlobEndpoint(), queueUri,
                sasAccount.getTableEndpoint(), sasAccount.getFileEndpoint());
        return sasAccount.createCloudQueueClient();
    }

    public static CloudTableClient createCloudTableClient(SharedAccessAccountPolicy policy, boolean useHttps)
            throws StorageException, InvalidKeyException, URISyntaxException {

        CloudStorageAccount sasAccount = getAccount();
        final String token = sasAccount.generateSharedAccessSignature(policy);
        final StorageCredentials creds =
                new StorageCredentialsSharedAccessSignature(token);

        final URI tableUri = new URI(useHttps ? "https" : "http", sasAccount.getTableEndpoint().getAuthority(), 
        		sasAccount.getTableEndpoint().getPath(), sasAccount.getTableEndpoint().getQuery(), null);
        
        sasAccount = new CloudStorageAccount(creds, sasAccount.getBlobEndpoint(), sasAccount.getQueueEndpoint(), 
        		tableUri, sasAccount.getFileEndpoint());
        return sasAccount.createCloudTableClient();
    }


    public static CloudAnalyticsClient createCloudAnalyticsClient() throws StorageException {
    	return getAccount().createCloudAnalyticsClient();
    }

	public static void verifyServiceStats(ServiceStats stats) {
		Assert.assertNotNull(stats);
		if (stats.getGeoReplication().getLastSyncTime() != null) {
			Assert.assertEquals(GeoReplicationStatus.LIVE, stats.getGeoReplication().getStatus());
		}
		else {
			Assert.assertTrue(stats.getGeoReplication().getStatus() == GeoReplicationStatus.BOOTSTRAP
					|| stats.getGeoReplication().getStatus() == GeoReplicationStatus.UNAVAILABLE);
		}
	}

	public static void assertByteArrayEquals(byte[] expected, byte[] actual) {
		if (expected == null || actual == null) {
			Assert.assertNull(expected);
			Assert.assertNull(actual);
		}
		else {
			Assert.assertEquals(expected.length, actual.length);
			for (int i = 0; i < expected.length; i++) {
				Assert.assertEquals(expected[i], actual[i]);
			}
		}
	}

	private static CloudStorageAccount getAccount() throws StorageException {
		// Only do this the first time TestBase is called as storage account is static
		if (account == null) {
			// if connectionString is set, use that as an account string
			// if accountName and accountKey are set, use those to setup the account with default endpoints
			// if all of the endpoints are set, use those to create custom endpoints
			try {
				if (connectionString != null) {
					account = CloudStorageAccount.parse(connectionString);
				}
				else if (accountName != null && accountKey != null) {
					StorageCredentialsAccountAndKey credentials = new StorageCredentialsAccountAndKey(accountName, accountKey);
					if(blobEndpoint == null || queueEndpoint == null || tableEndpoint == null){
						account = new CloudStorageAccount(credentials);
					} else {
						account = new CloudStorageAccount(credentials,blobEndpoint, queueEndpoint, tableEndpoint);
					}
				} else {
					throw new StorageException("CredentialsNotSpecified", 
							"Credentials must be specified in the TestHelper class in order to run tests.", 
							Constants.HeaderConstants.HTTP_UNUSED_306, 
							null,
							null);
				}
			}
			catch (Exception e) {
				throw StorageException.translateException(null, e, null);
			}
		}
		return account;
	}

    private static CloudStorageAccount getPremiumBlobAccount() throws StorageException {
        // Only do this the first time TestBase is called as storage account is static
        if (premiumAccount == null) {
            // if connectionString is set, use that as an account string
            // if accountName and accountKey are set, use those to setup the account with default endpoints
            // if all of the endpoints are set, use those to create custom endpoints
            try {
                if (premiumConnectionString != null) {
                    premiumAccount = CloudStorageAccount.parse(premiumConnectionString);
                }
                else if (premiumAccountName != null && premiumAccountKey != null) {
                    StorageCredentialsAccountAndKey credentials = new StorageCredentialsAccountAndKey(premiumAccountName, premiumAccountKey);
                    if(premiumBlobEndpoint == null){
                        premiumAccount = new CloudStorageAccount(credentials);
                    } else {
                        premiumAccount = new CloudStorageAccount(credentials, premiumBlobEndpoint, null , null);
                    }
                } else {
                    throw new StorageException("CredentialsNotSpecified",
                            "Credentials must be specified in the TestHelper class in order to run tests.",
                            Constants.HeaderConstants.HTTP_UNUSED_306,
                            null,
                            null);
                }
            }
            catch (Exception e) {
                throw StorageException.translateException(null, e, null);
            }
        }
        return premiumAccount;
    }

    protected static void enableFiddler() {
        System.setProperty("http.proxyHost", "localhost");
        System.setProperty("http.proxyPort", "8888");
    }

    public static byte[] getRandomBuffer(int length) {
        final Random randGenerator = new Random();
        final byte[] buff = new byte[length];
        randGenerator.nextBytes(buff);
        return buff;
    }

    public static ByteArrayInputStream getRandomDataStream(int length) {
        return new ByteArrayInputStream(getRandomBuffer(length));
    }

    public static void assertStreamsAreEqual(ByteArrayInputStream src, ByteArrayInputStream dst) {
        dst.reset();
        src.reset();
        Assert.assertEquals(src.available(), dst.available());

        while (src.available() > 0) {
            Assert.assertEquals(src.read(), dst.read());
        }

        Assert.assertFalse(dst.available() > 0);
    }

    public static void assertStreamsAreEqualAtIndex(ByteArrayInputStream src, ByteArrayInputStream dst, int srcIndex,
            int dstIndex, int length, int bufferSize) throws IOException {
        dst.reset();
        src.reset();

        dst.skip(dstIndex);
        src.skip(srcIndex);
        byte[] srcBuffer = new byte[bufferSize];
        byte[] destBuffer = new byte[bufferSize];
        src.read(srcBuffer);
        dst.read(destBuffer);

        for (int i = 0; i < length; i++) {
            Assert.assertEquals(src.read(), dst.read());
        }
    }

    public static URI defiddler(URI uri) throws URISyntaxException {
        String fiddlerString = "ipv4.fiddler";
        String replacementString = "127.0.0.1";

        String uriString = uri.toString();
        if (uriString.contains(fiddlerString)) {
            return new URI(uriString.replace(fiddlerString, replacementString));
        }
        else {
            return uri;
        }
    }

    public static void assertURIsEqual(URI expected, URI actual, boolean ignoreQueryOrder) {
        if (expected == null) {
        	Assert.assertEquals(null, actual);
        }

        Assert.assertEquals(expected.getScheme(), actual.getScheme());
        Assert.assertEquals(expected.getAuthority(), actual.getAuthority());
        Assert.assertEquals(expected.getPath(), actual.getPath());
        Assert.assertEquals(expected.getFragment(), actual.getFragment());

        ArrayList<String> expectedQueries = new ArrayList<String>(Arrays.asList(expected.getQuery().split("&")));
        ArrayList<String> actualQueries = new ArrayList<String>(Arrays.asList(actual.getQuery().split("&")));

        Assert.assertEquals(expectedQueries.size(), actualQueries.size());
        for (String expectedQuery : expectedQueries) {
        	Assert.assertTrue(expectedQuery, actualQueries.remove(expectedQuery));
        }

        Assert.assertTrue(actualQueries.isEmpty());
    }
} public class TestRunners {

    // Speed test category
    public interface SlowTests {
    }

    // Tenant type test categories
    public interface DevStoreTests {
    }

    public interface CloudTests {
    }

    public interface DevFabricTests {
    }

    public interface PremiumBlobTests {
    }

    // Test suites
    @RunWith(Suite.class)
    @SuiteClasses({AccountSasTests.class, EventFiringTests.class, GenericTests.class, MaximumExecutionTimeTests.class,
            SecondaryTests.class, ServicePropertiesTests.class, StorageAccountTests.class,
            StorageUriTests.class})
    public static class CoreTestSuite {
    }

    @RunWith(Suite.class)
    @SuiteClasses({BlobOutputStreamTests.class, CloudBlobClientTests.class, CloudBlobContainerTests.class,
            CloudBlobDirectoryTests.class, CloudAppendBlobTests.class, CloudBlockBlobTests.class, CloudPageBlobTests.class,
            LeaseTests.class, SasTests.class, PremiumBlobTests.class})
    public static class BlobTestSuite {
    }

    @RunWith(Suite.class)
    @SuiteClasses({CloudQueueClientGB18030Test.class, CloudQueueClientTests.class, CloudQueueTests.class})
    public static class QueueTestSuite {
    }

    @RunWith(Suite.class)
    @SuiteClasses({TableBatchOperationTests.class, TableClientTests.class, TableDateTests.class, TableEscapingTests.class,
            TableODataTests.class, TableOperationTests.class, TableQueryTests.class, TableSerializerTests.class, TableTests.class})
    public static class TableTestSuite {
    }

    @RunWith(Suite.class)
    @SuiteClasses({CloudFileClientTests.class, CloudFileDirectoryTests.class, CloudFileShareTests.class,
            CloudFileTests.class, FileSasTests.class})
    public static class FileTestSuite {
    }

    @RunWith(Suite.class)
    @SuiteClasses({CloudAnalyticsClientTests.class})
    public static class AnalyticsTestSuite {
    }

    @RunWith(Suite.class)
    @SuiteClasses({CloudBlobServerEncryptionTests.class})
    public static class EncryptionTestSuite {
    }

    @RunWith(Suite.class)
    @SuiteClasses({CoreTestSuite.class, BlobTestSuite.class, QueueTestSuite.class, TableTestSuite.class,
            FileTestSuite.class, AnalyticsTestSuite.class})
    public static class AllTestSuite {
    }

    // Test suites with categories:

    @RunWith(Categories.class)
    @IncludeCategory(DevFabricTests.class)
    @ExcludeCategory(SlowTests.class)
    @SuiteClasses(AllTestSuite.class)
    public static class DevFabricFastTestSuite {
    }

    @RunWith(Categories.class)
    @IncludeCategory(DevFabricTests.class)
    @ExcludeCategory(SecondaryTests.class)
    @SuiteClasses(AllTestSuite.class)
    public static class DevFabricNoSecondarySuite {
    }

    @RunWith(Categories.class)
    @ExcludeCategory(SlowTests.class)
    @SuiteClasses(AllTestSuite.class)
    public static class FastTestSuite {
    }

    @RunWith(Categories.class)
    @IncludeCategory(PremiumBlobTests.class)
    public static class PremiumBlobTestSuite {
    }
} public interface CloudTests {
} public interface DevFabricTests {
} public interface DevStoreTests {
} public interface SlowTests {
} public final class CloudBlobContainer {

    /**
     * Converts the ACL string to a BlobContainerPermissions object.
     * 
     * @param aclString
     *            A <code>String</code> which specifies the ACLs to convert.
     * 
     * @return A {@link BlobContainerPermissions} object which represents the ACLs.
     */
    static BlobContainerPermissions getContainerAcl(final String aclString) {
        BlobContainerPublicAccessType accessType = BlobContainerPublicAccessType.parse(aclString);

        final BlobContainerPermissions retVal = new BlobContainerPermissions();
        retVal.setPublicAccess(accessType);

        return retVal;
    }

    /**
     * Represents the container metadata.
     */
    protected HashMap<String, String> metadata = new HashMap<String, String>();

    /**
     * Holds the container properties.
     */
    BlobContainerProperties properties = new BlobContainerProperties();

    /**
     * Holds the name of the container.
     */
    String name;

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

    /**
     * Holds a reference to the associated service client.
     */
    private CloudBlobClient blobServiceClient;
    
    /**
     * Creates an instance of the <code>CloudBlobContainer</code> class using the specified URI. The blob URI should
     * include a SAS token unless anonymous access is to be used.
     * 
     * @param uri
     *            A <code>java.net.URI</code> object which represents the URI of the container.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudBlobContainer(final URI uri) throws StorageException {
        this(new StorageUri(uri));
    }

    /**
     * Creates an instance of the <code>CloudBlobContainer</code> class using the specified URI. The blob URI should
     * include a SAS token unless anonymous access is to be used.
     * 
     * @param storageUri
     *            A {@link StorageUri} object which represents the URI of the container.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudBlobContainer(final StorageUri storageUri) throws StorageException {
        this(storageUri, (StorageCredentials) null);
    }
    
    /**
     * Creates an instance of the <code>CloudBlobContainer</code> class using the specified URI and credentials.
     * 
     * @param uri
     *            A <code>java.net.URI</code> object that represents the absolute URI of the container.
     * @param credentials
     *            A {@link StorageCredentials} object used to authenticate access.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudBlobContainer(final URI uri, final StorageCredentials credentials) throws StorageException {
        this(new StorageUri(uri), credentials);
    }

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

    /**
     * Creates an instance of the <code>CloudBlobContainer</code> class using the specified name and client.
     * 
     * @param containerName
     *            A <code>String</code> which represents the name of the container, which must adhere to container
     *            naming rules.
     *            The container name should not include any path separator characters (/).
     *            Container names must be lowercase, between 3-63 characters long and must start with a letter or
     *            number. Container names may contain only letters, numbers, and the dash (-) character.
     * @param client
     *            A {@link CloudBlobClient} object that represents the associated service client, and that specifies the
     *            endpoint for the Blob service. *
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI constructed based on the containerName is invalid.
     * 
     * @see <a href="http://msdn.microsoft.com/library/azure/dd135715.aspx">Naming and Referencing Containers, Blobs,
     *      and Metadata</a>
     */
    protected CloudBlobContainer(final String containerName, final CloudBlobClient client) throws URISyntaxException,
            StorageException {
        Utility.assertNotNull("client", client);
        Utility.assertNotNull("containerName", containerName);

        this.storageUri = PathUtility.appendPathToUri(client.getStorageUri(), containerName);
        this.name = containerName;
        this.blobServiceClient = client;
    }

    /**
     * Creates the container.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void create() throws StorageException {
        this.create(BlobContainerPublicAccessType.OFF, null /* options */, null /* opContext */);
    }

    /**
     * Creates the container using the specified options and operation context.
     *
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 create(BlobRequestOptions options, OperationContext opContext) throws StorageException {
        this.create(BlobContainerPublicAccessType.OFF, options, opContext);
    }

    /**
     * Creates the container using the specified options and operation context.
     *
     * @param accessType
     *            A {@link BlobContainerPublicAccessType} object that specifies whether data in the container may be
     *            accessed publicly and what level of access is to be allowed.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 create(BlobContainerPublicAccessType accessType, BlobRequestOptions options, OperationContext opContext) throws StorageException {
        if (accessType == BlobContainerPublicAccessType.UNKNOWN) {
            throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.ARGUMENT_OUT_OF_RANGE_ERROR, "accessType", accessType));
        }

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        ExecutionEngine.executeWithRetry(
                this.blobServiceClient, this, createImpl(options, accessType),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlobContainer, Void> createImpl(
            final BlobRequestOptions options, final BlobContainerPublicAccessType accessType) {
        final StorageRequest<CloudBlobClient, CloudBlobContainer, Void> putRequest = new StorageRequest<CloudBlobClient, CloudBlobContainer, Void>(
                options, this.getStorageUri()) {
            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                    OperationContext context) throws Exception {
                final HttpURLConnection request = BlobRequest.createContainer(
                        container.getTransformedAddress().getUri(this.getCurrentLocation()), options, context, accessType);
                return request;
            }

            @Override
            public void setHeaders(HttpURLConnection connection, CloudBlobContainer container, OperationContext context) {
                BlobRequest.addMetadata(connection, container.metadata, context);
            }

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

            @Override
            public Void preProcessResponse(CloudBlobContainer container, CloudBlobClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                // Set attributes
                final BlobContainerAttributes attributes = BlobResponse.getBlobContainerAttributes(
                        this.getConnection(), client.isUsePathStyleUris());
                container.properties = attributes.getProperties();
                container.name = attributes.getName();
                if (accessType != null) {
                    container.properties.setPublicAccess(accessType);
                }
                else {
                    container.properties.setPublicAccess(BlobContainerPublicAccessType.OFF);
                }

                return null;
            }
        };

        return putRequest;
    }

    /**
     * Creates the container if it does not exist.
     * 
     * @return <code>true</code> if the container did not already exist and was created; otherwise, <code>false</code>.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public boolean createIfNotExists() throws StorageException {
        return this.createIfNotExists(BlobContainerPublicAccessType.OFF, null /* options */, null /* opContext */);
    }

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

    /**
     * Creates the container if it does not exist, using the specified request options and operation context.
     * 
     * @param accessType
     *            A {@link BlobContainerPublicAccessType} object that specifies whether data in the container may be
     *            accessed publicly and what level of access is to be allowed.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request.
     *            Specifying <code>null</code> will use the default request options from the associated service client
     *            ({@link 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 <code>true</code> if the container did not already exist and was created; otherwise, <code>false</code>.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public boolean createIfNotExists(BlobContainerPublicAccessType accessType, BlobRequestOptions options, OperationContext opContext) throws StorageException {
        if (accessType == BlobContainerPublicAccessType.UNKNOWN) {
            throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.ARGUMENT_OUT_OF_RANGE_ERROR, "accessType", accessType));
        }

        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        boolean exists = this.exists(true /* primaryOnly */, null /* accessCondition */, options, opContext);
        if (exists) {
            return false;
        }
        else {
            try {
                this.create(accessType, options, opContext);
                return true;
            }
            catch (StorageException e) {
                if (e.getHttpStatusCode() == HttpURLConnection.HTTP_CONFLICT
                        && StorageErrorCodeStrings.CONTAINER_ALREADY_EXISTS.equals(e.getErrorCode())) {
                    return false;
                }
                else {
                    throw e;
                }
            }
        }
    }

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

    /**
     * Deletes the container using the specified request options and operation context.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the container.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 delete(AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        ExecutionEngine.executeWithRetry(this.blobServiceClient, this, deleteImpl(accessCondition, options),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlobContainer, Void> deleteImpl(
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlobContainer, Void> putRequest =
                new StorageRequest<CloudBlobClient, CloudBlobContainer, Void>(options, this.getStorageUri()) {
            @Override
            public HttpURLConnection buildRequest(
                    CloudBlobClient client, CloudBlobContainer container, OperationContext context) throws Exception {
                return BlobRequest.deleteContainer(
                        container.getTransformedAddress().getPrimaryUri(), options, context, accessCondition);
            }

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

            @Override
            public Void preProcessResponse(CloudBlobContainer container, CloudBlobClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) {
                    this.setNonExceptionedRetryableFailure(true);
                }

                return null;
            }
        };

        return putRequest;
    }

    /**
     * Deletes the container if it exists.
     * 
     * @return <code>true</code> if the container did not already exist and was created; otherwise, <code>false</code>.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public boolean deleteIfExists() throws StorageException {
        return this.deleteIfExists(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Deletes the container if it exists using the specified request options and operation context.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the container.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 <code>true</code> if the container existed and was deleted; otherwise, <code>false</code>.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public boolean deleteIfExists(AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        boolean exists = this.exists(true /* primaryOnly */, accessCondition, options, opContext);
        if (exists) {
            try {
                this.delete(accessCondition, options, opContext);
                return true;
            }
            catch (StorageException e) {
                if (e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND
                        && StorageErrorCodeStrings.CONTAINER_NOT_FOUND.equals(e.getErrorCode())) {
                    return false;
                }
                else {
                    throw e;
                }
            }
        }
        else {
            return false;
        }
    }

    /**
     * Downloads the container's attributes, which consist of metadata and properties.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void downloadAttributes() throws StorageException {
        this.downloadAttributes(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Downloads the container's attributes, which consist of metadata and properties, using the specified request
     * options and operation context.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the container.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 downloadAttributes(AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.downloadAttributesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlobContainer, Void> downloadAttributesImpl(
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlobContainer, Void> getRequest = new StorageRequest<CloudBlobClient, CloudBlobContainer, Void>(
                options, this.getStorageUri()) {

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

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                    OperationContext context) throws Exception {
                return BlobRequest.getContainerProperties(
                        container.getTransformedAddress().getUri(this.getCurrentLocation()), options, context,
                        accessCondition);
            }

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

            @Override
            public Void preProcessResponse(CloudBlobContainer container, CloudBlobClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                // Set attributes
                final BlobContainerAttributes attributes = BlobResponse.getBlobContainerAttributes(
                        this.getConnection(), client.isUsePathStyleUris());
                container.metadata = attributes.getMetadata();
                container.properties = attributes.getProperties();
                container.name = attributes.getName();
                return null;
            }
        };

        return getRequest;
    }

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

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

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

    private StorageRequest<CloudBlobClient, CloudBlobContainer, BlobContainerPermissions> downloadPermissionsImpl(
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlobContainer, BlobContainerPermissions> getRequest = new StorageRequest<CloudBlobClient, CloudBlobContainer, BlobContainerPermissions>(
                options, this.getStorageUri()) {

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

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                    OperationContext context) throws Exception {
                return BlobRequest.getAcl(container.getTransformedAddress().getUri(this.getCurrentLocation()), options,
                        accessCondition, context);
            }

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

            @Override
            public BlobContainerPermissions preProcessResponse(CloudBlobContainer container, CloudBlobClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                }

                container.updatePropertiesFromResponse(this.getConnection());
                final String aclString = BlobResponse.getAcl(this.getConnection());
                final BlobContainerPermissions containerAcl = getContainerAcl(aclString);
                container.properties.setPublicAccess(containerAcl.getPublicAccess());
                return containerAcl;
            }

            @Override
            public BlobContainerPermissions postProcessResponse(HttpURLConnection connection,
                    CloudBlobContainer container, CloudBlobClient client, OperationContext context,
                    BlobContainerPermissions containerAcl) throws Exception {
                HashMap<String, SharedAccessBlobPolicy> accessIds = SharedAccessPolicyHandler.getAccessIdentifiers(this
                        .getConnection().getInputStream(), SharedAccessBlobPolicy.class);

                for (final String key : accessIds.keySet()) {
                    containerAcl.getSharedAccessPolicies().put(key, accessIds.get(key));
                }

                return containerAcl;
            }
        };

        return getRequest;
    }

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

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

    @DoesServiceRequest
    private boolean exists(final boolean primaryOnly, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.existsImpl(primaryOnly, accessCondition, options), options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlobContainer, Boolean> existsImpl(final boolean primaryOnly,
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlobContainer, Boolean> getRequest = new StorageRequest<CloudBlobClient, CloudBlobContainer, Boolean>(
                options, this.getStorageUri()) {

            @Override
            public void setRequestLocationMode() {
                this.setRequestLocationMode(primaryOnly ? RequestLocationMode.PRIMARY_ONLY
                        : RequestLocationMode.PRIMARY_OR_SECONDARY);
            }

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                    OperationContext context) throws Exception {
                return BlobRequest.getContainerProperties(
                        container.getTransformedAddress().getUri(this.getCurrentLocation()), options, context,
                        accessCondition);
            }

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

            @Override
            public Boolean preProcessResponse(CloudBlobContainer container, CloudBlobClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) {
                    // Set attributes
                    final BlobContainerAttributes attributes = BlobResponse.getBlobContainerAttributes(
                            this.getConnection(), client.isUsePathStyleUris());
                    container.metadata = attributes.getMetadata();
                    container.properties = attributes.getProperties();
                    container.name = attributes.getName();
                    return Boolean.valueOf(true);
                }
                else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
                    return Boolean.valueOf(false);
                }
                else {
                    this.setNonExceptionedRetryableFailure(true);
                    // return false instead of null to avoid SCA issues
                    return false;
                }
            }
        };

        return getRequest;
    }

    /**
     * Returns a shared access signature for the container. Note this does not contain the leading "?".
     * 
     * @param policy
     *            An {@link SharedAccessBlobPolicy} object that represents the access policy for the shared access
     *            signature.
     * @param groupPolicyIdentifier
     *            A <code>String</code> which represents the container-level access policy.
     * 
     * @return A <code>String</code> which represents a shared access signature for the container.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws InvalidKeyException
     *             If the key is invalid.
     */
    public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy, final String groupPolicyIdentifier)
            throws InvalidKeyException, StorageException {

        return this.generateSharedAccessSignature(
                policy, groupPolicyIdentifier, null /* IP range */, null /* protocols */);
    }
    
    /**
     * Returns a shared access signature for the container. Note this does not contain the leading "?".
     * 
     * @param policy
     *            An {@link SharedAccessBlobPolicy} object that represents the access policy for the shared access
     *            signature.
     * @param groupPolicyIdentifier
     *            A <code>String</code> which represents the container-level access policy.
     * @param ipRange
     *            A {@link IPRange} object containing the range of allowed IP addresses.
     * @param protocols
     *            A {@link SharedAccessProtocols} representing the allowed Internet protocols.
     * 
     * @return A <code>String</code> which represents a shared access signature for the container.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws InvalidKeyException
     *             If the key is invalid.
     */
    public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy,
            final String groupPolicyIdentifier, final IPRange ipRange, final SharedAccessProtocols protocols)
            throws InvalidKeyException, StorageException {
        if (!StorageCredentialsHelper.canCredentialsSignRequest(this.blobServiceClient.getCredentials())) {
            final String errorMessage = SR.CANNOT_CREATE_SAS_WITHOUT_ACCOUNT_KEY;
            throw new IllegalArgumentException(errorMessage);
        }
        
        final String resourceName = this.getSharedAccessCanonicalName();

        final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForBlobAndFile(
                policy, null /* SharedAccessBlobHeaders */, groupPolicyIdentifier, resourceName,
                ipRange, protocols, this.blobServiceClient);

        final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForBlobAndFile(policy,
                null /* SharedAccessBlobHeaders */, groupPolicyIdentifier, "c", ipRange, protocols, signature);

        return builder.toString();
    }

    /**
     * Returns a reference to a {@link CloudAppendBlob} object that represents an append blob in this container.
     * 
     * @param blobName
     *            A <code>String</code> that represents the name of the blob.
     * 
     * @return A {@link CloudAppendBlob} object that represents a reference to the specified append blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    public CloudAppendBlob getAppendBlobReference(final String blobName) throws URISyntaxException, StorageException {
        return this.getAppendBlobReference(blobName, null);
    }

    /**
     * Returns a reference to a {@link CloudAppendBlob} object that represents an append blob in the container, using the
     * specified snapshot ID.
     * 
     * @param blobName
     *            A <code>String</code> that represents the name of the blob.
     * @param snapshotID
     *            A <code>String</code> that represents the snapshot ID of the blob.
     * 
     * @return A {@link CloudAppendBlob} object that represents a reference to the specified append blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    public CloudAppendBlob getAppendBlobReference(final String blobName, final String snapshotID)
            throws URISyntaxException, StorageException {
        return new CloudAppendBlob(blobName, snapshotID, this);
    }
    
    /**
     * Returns a reference to a {@link CloudBlockBlob} object that represents a block blob in this container.
     * 
     * @param blobName
     *            A <code>String</code> that represents the name of the blob.
     * 
     * @return A {@link CloudBlockBlob} object that represents a reference to the specified block blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    public CloudBlockBlob getBlockBlobReference(final String blobName) throws URISyntaxException, StorageException {
        return this.getBlockBlobReference(blobName, null);
    }

    /**
     * Returns a reference to a {@link CloudBlockBlob} object that represents a block blob in this container, using the
     * specified snapshot ID.
     * 
     * @param blobName
     *            A <code>String</code> that represents the name of the blob.
     * @param snapshotID
     *            A <code>String</code> that represents the snapshot ID of the blob.
     * 
     * @return A {@link CloudBlockBlob} object that represents a reference to the specified block blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    public CloudBlockBlob getBlockBlobReference(final String blobName, final String snapshotID)
            throws URISyntaxException, StorageException {
        return new CloudBlockBlob(blobName, snapshotID, this);
    }

    /**
     * Returns a reference to a {@link CloudBlobDirectory} object that represents a virtual blob directory within this
     * container.
     * 
     * @param directoryName
     *            A <code>String</code> that represents the name of the virtual blob directory. If the root directory
     *            (the directory representing the container itself) is desired, use an empty string.
     * @return A {@link CloudBlobDirectory} that represents a virtual blob directory within this container.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    public CloudBlobDirectory getDirectoryReference(String directoryName) throws URISyntaxException {
        Utility.assertNotNull("directoryName", directoryName);

        // if the directory name does not end in the delimiter, add the delimiter
        if (!directoryName.isEmpty() && !directoryName.endsWith(this.blobServiceClient.getDirectoryDelimiter())) {
            directoryName = directoryName.concat(this.blobServiceClient.getDirectoryDelimiter());
        }

        final StorageUri address = PathUtility.appendPathToUri(this.storageUri, directoryName);

        return new CloudBlobDirectory(address, directoryName, this.blobServiceClient, this);
    }
    
    
    /**
     * Gets a reference to a blob in this container. The blob must already exist on the service.
     * 
     * Unlike the other get*Reference methods, this method does a service request to retrieve the blob's metadata and 
     * properties. The returned blob may be used directly as a CloudBlob or cast using either instanceof or 
     * getProperties().getBlobType() to determine its subtype.
     *
     * @param blobName
     *            A <code>String</code> that represents the name of the blob.
     *
     * @return A {@link CloudBlob} object that represents a reference to the specified blob.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    @DoesServiceRequest
    public final CloudBlob getBlobReferenceFromServer(final String blobName) throws URISyntaxException, StorageException {
        return this.getBlobReferenceFromServer(blobName, null, null, null, null);
    }

    /**
     * Gets a reference to a blob in this container. The blob must already exist on the service.
     * 
     * Unlike the other get*Reference methods, this method does a service request to retrieve the blob's metadata and
     * properties. The returned blob may be used directly as a CloudBlob or cast using either instanceof or
     * getProperties().getBlobType() to determine its subtype.
     * 
     * @param blobName
     *            A <code>String</code> that represents the name of the blob.
     * @param snapshotID
     *            A <code>String</code> that represents the snapshot ID of the blob.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 A {@link CloudBlob} object that represents a reference to the specified blob.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    @DoesServiceRequest
    public final CloudBlob getBlobReferenceFromServer(final String blobName, final String snapshotID,
            AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws URISyntaxException, StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);
        
        StorageUri blobUri = PathUtility.appendPathToUri(this.getStorageUri(), blobName);
        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.getBlobReferenceFromServerImpl(blobName, blobUri, snapshotID, accessCondition, options), options.getRetryPolicyFactory(), opContext);
    }
    
    private StorageRequest<CloudBlobClient, CloudBlobContainer, CloudBlob> getBlobReferenceFromServerImpl(
            final String blobName, final StorageUri blobUri, final String snapshotID, final AccessCondition accessCondition,
            final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlobContainer, CloudBlob> getRequest = new StorageRequest<CloudBlobClient, CloudBlobContainer, CloudBlob>(
                options, this.getStorageUri()) {

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

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                    OperationContext context) throws Exception {
                StorageUri transformedblobUri = CloudBlobContainer.this.getServiceClient().getCredentials().transformUri(blobUri, context);                 
                return BlobRequest.getBlobProperties(transformedblobUri.getUri(this.getCurrentLocation()), options, context, accessCondition, snapshotID);
            }

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

            @Override
            public CloudBlob preProcessResponse(CloudBlobContainer container, CloudBlobClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                // Set attributes
                final BlobAttributes retrievedAttributes = BlobResponse.getBlobAttributes(this.getConnection(),
                        blobUri, snapshotID);

                CloudBlob blob;
                switch (retrievedAttributes.getProperties().getBlobType()) {
                    case APPEND_BLOB:
                        blob = container.getAppendBlobReference(blobName, snapshotID);
                        break;
    
                    case BLOCK_BLOB:
                        blob = container.getBlockBlobReference(blobName, snapshotID);
                        break;
    
                    case PAGE_BLOB:
                        blob = container.getPageBlobReference(blobName, snapshotID);
                        break;
    
                    default:
                        throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE,
                                SR.INVALID_RESPONSE_RECEIVED, Constants.HeaderConstants.HTTP_UNUSED_306, null, null);
                }

                blob.properties = retrievedAttributes.getProperties();
                blob.metadata = retrievedAttributes.getMetadata();

                return blob;
            }
        };

        return getRequest;
    }
    
    /**
     * Returns the metadata for the container. This value is initialized with the metadata from the queue by a call to
     * {@link #downloadAttributes}, and is set on the queue with a call to {@link #uploadMetadata}.
     * 
     * @return A <code>java.util.HashMap</code> object that represents the metadata for the container.
     */
    public HashMap<String, String> getMetadata() {
        return this.metadata;
    }

    /**
     * Returns the name of the container.
     * 
     * @return A <code>String</code> that represents the name of the container.
     */
    public String getName() {
        return this.name;
    }

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

    /**
     * Returns a reference to a {@link CloudPageBlob} object that represents a page blob in this container.
     * 
     * @param blobName
     *            A <code>String</code> that represents the name of the blob.
     * 
     * @return A {@link CloudPageBlob} object that represents a reference to the specified page blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    public CloudPageBlob getPageBlobReference(final String blobName) throws URISyntaxException, StorageException {
        return this.getPageBlobReference(blobName, null);
    }

    /**
     * Returns a reference to a {@link CloudPageBlob} object that represents a page blob in the container, using the
     * specified snapshot ID.
     * 
     * @param blobName
     *            A <code>String</code> that represents the name of the blob.
     * @param snapshotID
     *            A <code>String</code> that represents the snapshot ID of the blob.
     * 
     * @return A {@link CloudPageBlob} object that represents a reference to the specified page blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    public CloudPageBlob getPageBlobReference(final String blobName, final String snapshotID)
            throws URISyntaxException, StorageException {
        return new CloudPageBlob(blobName, snapshotID, this);
    }

    /**
     * Returns the properties for the container.
     * 
     * @return A {@link BlobContainerProperties} object that represents the properties for the container.
     */
    public BlobContainerProperties getProperties() {
        return this.properties;
    }

    /**
     * Returns the Blob service client associated with this container.
     * 
     * @return A {@link CloudBlobClient} object that represents the service client associated with this container.
     */
    public CloudBlobClient getServiceClient() {
        return this.blobServiceClient;
    }

    /**
     * Returns the canonical name for shared access.
     * 
     * @return the canonical name for shared access.
     */
    private String getSharedAccessCanonicalName() {
        String accountName = this.getServiceClient().getCredentials().getAccountName();
        String containerName = this.getName();

        return String.format("/%s/%s/%s", SR.BLOB, accountName, containerName);
    }

    /**
     * Returns the URI after applying the authentication transformation.
     * 
     * @return A <code>java.net.URI</code> object that represents the URI after applying the authentication
     *         transformation.
     * 
     * @throws IllegalArgumentException
     *             If an unexpected value is passed.
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    private StorageUri getTransformedAddress() throws URISyntaxException, StorageException {
        return this.blobServiceClient.getCredentials().transformUri(this.storageUri);
    }

    /**
     * Returns the URI for this container.
     * 
     * @return The absolute URI to the container.
     */
    public URI getUri() {
        return this.storageUri.getPrimaryUri();
    }

    /**
     * Returns an enumerable collection of blob items for the container.
     * 
     * @return An enumerable collection of {@link ListBlobItem} objects retrieved lazily that represents the items in
     *         this container.
     */
    @DoesServiceRequest
    public Iterable<ListBlobItem> listBlobs() {
        return this.listBlobs(null, false, EnumSet.noneOf(BlobListingDetails.class), null, null);
    }

    /**
     * Returns an enumerable collection of blob items for the container whose names begin with the specified prefix.
     * 
     * @param prefix
     *            A <code>String</code> that represents the blob name prefix. This value must be preceded either by the
     *            name of the container or by the absolute path to the container.
     * 
     * @return An enumerable collection of {@link ListBlobItem} objects retrieved lazily that represents the
     *         items whose names begin with the specified prefix in this container.
     */
    @DoesServiceRequest
    public Iterable<ListBlobItem> listBlobs(final String prefix) {
        return this.listBlobs(prefix, false);
    }
    
    /**
     * Returns an enumerable collection of blob items for the container whose names begin with the specified prefix
     * using the specified flat or hierarchical option.
     * 
     * @param prefix
     *            A <code>String</code> that represents the blob name prefix. This value must be preceded either by the
     *            name of the container or by the absolute path to the container.
     * @param useFlatBlobListing
     *            <code>true</code> to indicate that the returned list will be flat; <code>false</code> to indicate that
     *            the returned list will be hierarchical.
     * @return An enumerable collection of {@link ListBlobItem} objects retrieved lazily that represents the
     *         items whose names begin with the specified prefix in this container.
     */
    @DoesServiceRequest
    public Iterable<ListBlobItem> listBlobs(final String prefix, final boolean useFlatBlobListing) {
        return this.listBlobs(prefix, useFlatBlobListing, EnumSet.noneOf(BlobListingDetails.class), null, null);
    }

    /**
     * Returns an enumerable collection of blob items for the container whose names begin with the specified prefix, 
     * using the specified flat or hierarchical option, listing details options, request options, and operation context.
     * 
     * @param prefix
     *            A <code>String</code> that represents the blob name prefix. This value must be preceded either by the
     *            name of the container or by the absolute path to the container.
     * @param useFlatBlobListing
     *            <code>true</code> to indicate that the returned list will be flat; <code>false</code> to indicate that
     *            the returned list will be hierarchical.
     * @param listingDetails
     *            A <code>java.util.EnumSet</code> object that contains {@link BlobListingDetails} values that indicate
     *            whether snapshots, metadata, and/or uncommitted blocks are returned. Committed blocks are always
     *            returned.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 An enumerable collection of {@link ListBlobItem} objects retrieved lazily that represents the
     *         items whose names begin with the specified prefix in this container.
     */
    @DoesServiceRequest
    public Iterable<ListBlobItem> listBlobs(final String prefix, final boolean useFlatBlobListing,
            final EnumSet<BlobListingDetails> listingDetails, BlobRequestOptions options, OperationContext opContext) {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        if (!useFlatBlobListing && listingDetails != null && listingDetails.contains(BlobListingDetails.SNAPSHOTS)) {
            throw new IllegalArgumentException(SR.SNAPSHOT_LISTING_ERROR);
        }

        SegmentedStorageRequest segmentedRequest = new SegmentedStorageRequest();

        return new LazySegmentedIterable<CloudBlobClient, CloudBlobContainer, ListBlobItem>(
                this.listBlobsSegmentedImpl(prefix, useFlatBlobListing, listingDetails, null, options, segmentedRequest),
                this.blobServiceClient, this, options.getRetryPolicyFactory(), opContext);
    }

    /**
     * Returns a result segment of an enumerable collection of blob items in the container.
     * 
     * @return A {@link ResultSegment} object that contains a segment of the enumerable collection of
     *         {@link ListBlobItem} objects that represent the blob items in the container.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ResultSegment<ListBlobItem> listBlobsSegmented() throws StorageException {
        return this.listBlobsSegmented(null, false, EnumSet.noneOf(BlobListingDetails.class), null, null, null, null);
    }

    /**
     * Returns a result segment containing a collection of blob items whose names begin with the specified prefix.
     * 
     * @param prefix
     *            A <code>String</code> that represents the prefix of the blob name.
     * 
     * @return A {@link ResultSegment} object that contains a segment of the enumerable collection of
     *         {@link ListBlobItem} objects that represent the blob items whose names begin with the specified prefix in
     *         the container.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ResultSegment<ListBlobItem> listBlobsSegmented(final String prefix) throws StorageException {
        return this.listBlobsSegmented(prefix, false, EnumSet.noneOf(BlobListingDetails.class), null, null, null, null);
    }

    /**
     * Returns a result segment containing a collection of blob items whose names begin with the
     * specified prefix, using the specified flat or hierarchical option, listing details options,
     * request options, and operation context.
     * 
     * @param prefix
     *            A <code>String</code> that represents the prefix of the blob name.
     * @param useFlatBlobListing
     *            <code>true</code> to indicate that the returned list will be flat;
     *            <code>false</code> to indicate that the returned list will be hierarchical.
     * @param listingDetails
     *            A <code>java.util.EnumSet</code> object that contains {@link BlobListingDetails} 
     *            values that indicate whether snapshots, metadata, and/or uncommitted blocks
     *            are returned. Committed blocks are always returned.
     * @param maxResults
     *            The maximum number of results to retrieve.  If <code>null</code> 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 BlobRequestOptions} object that specifies any additional options for the
     *            request. Specifying <code>null</code> will use the default request options from
     *            the associated service client ({@link 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 A {@link ResultSegment} object that contains a segment of the enumerable collection
     *         of {@link ListBlobItem} objects that represent the block items whose names begin
     *         with the specified prefix in the container.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ResultSegment<ListBlobItem> listBlobsSegmented(final String prefix, final boolean useFlatBlobListing,
            final EnumSet<BlobListingDetails> listingDetails, final Integer maxResults,
            final ResultContinuation continuationToken, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        Utility.assertContinuationType(continuationToken, ResultContinuationType.BLOB);

        if (!useFlatBlobListing && listingDetails != null && listingDetails.contains(BlobListingDetails.SNAPSHOTS)) {
            throw new IllegalArgumentException(SR.SNAPSHOT_LISTING_ERROR);
        }

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

        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.listBlobsSegmentedImpl(prefix,
                useFlatBlobListing, listingDetails, maxResults, options, segmentedRequest), options
                .getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlobContainer, ResultSegment<ListBlobItem>> listBlobsSegmentedImpl(
            final String prefix, final boolean useFlatBlobListing, final EnumSet<BlobListingDetails> listingDetails,
            final Integer maxResults, final BlobRequestOptions options, final SegmentedStorageRequest segmentedRequest) {

        Utility.assertContinuationType(segmentedRequest.getToken(), ResultContinuationType.BLOB);
        Utility.assertNotNull("options", options);

        final String delimiter = useFlatBlobListing ? null : this.blobServiceClient.getDirectoryDelimiter();

        final BlobListingContext listingContext = new BlobListingContext(prefix, maxResults, delimiter, listingDetails);

        final StorageRequest<CloudBlobClient, CloudBlobContainer, ResultSegment<ListBlobItem>> getRequest = new StorageRequest<CloudBlobClient, CloudBlobContainer, ResultSegment<ListBlobItem>>(
                options, this.getStorageUri()) {

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

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                    OperationContext context) throws Exception {
                listingContext.setMarker(segmentedRequest.getToken() != null ? segmentedRequest.getToken()
                        .getNextMarker() : null);
                return BlobRequest.listBlobs(container.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, listingContext);
            }

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

            @Override
            public ResultSegment<ListBlobItem> preProcessResponse(CloudBlobContainer container, CloudBlobClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                }

                return null;
            }

            @Override
            public ResultSegment<ListBlobItem> postProcessResponse(HttpURLConnection connection,
                    CloudBlobContainer container, CloudBlobClient client, OperationContext context,
                    ResultSegment<ListBlobItem> storageObject) throws Exception {
                final ListBlobsResponse response = BlobListHandler.getBlobList(connection.getInputStream(), container);

                ResultContinuation newToken = null;

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

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

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

                return resSegment;
            }
        };

        return getRequest;
    }

    /**
     * Returns an enumerable collection of containers for the service client associated with this container.
     * 
     * @return An enumerable collection of {@link CloudBlobContainer} objects retrieved lazily that represent the
     *         containers for the service client associated with this container.
     */
    @DoesServiceRequest
    public Iterable<CloudBlobContainer> listContainers() {
        return this.blobServiceClient.listContainers();
    }

    /**
     * Returns an enumerable collection of containers whose names begin with the specified prefix for the service client
     * associated with this container.
     * 
     * @param prefix
     *            A <code>String</code> that represents the container name prefix.
     * 
     * @return An enumerable collection of {@link CloudBlobContainer} objects retrieved lazily that represent the
     *         containers whose names begin with the specified prefix for the service client associated with this
     *         container.
     */
    @DoesServiceRequest
    public Iterable<CloudBlobContainer> listContainers(final String prefix) {
        return this.blobServiceClient.listContainers(prefix);
    }

    /**
     * Returns an enumerable collection of containers whose names begin with the specified prefix for the service client
     * associated with this container, using the specified details setting, request options, and operation context.
     * 
     * @param prefix
     *            A <code>String</code> that represents the container name prefix.
     * @param detailsIncluded
     *            A {@link ContainerListingDetails} value that indicates whether container metadata will be returned.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 An enumerable collection of {@link CloudBlobContainer} objects retrieved lazily that represents the
     *         containers for the
     *         service client associated with this container.
     */
    @DoesServiceRequest
    public Iterable<CloudBlobContainer> listContainers(final String prefix,
            final ContainerListingDetails detailsIncluded, final BlobRequestOptions options,
            final OperationContext opContext) {
        return this.blobServiceClient.listContainers(prefix, detailsIncluded, options, opContext);
    }

    /**
     * Returns a result segment of an enumerable collection of containers for the service client associated with this
     * container.
     * 
     * @return A {@link ResultSegment} object that contains a segment of the enumerable collection of
     *         {@link CloudBlobContainer} objects that represent the containers for the service client associated with
     *         this container.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ResultSegment<CloudBlobContainer> listContainersSegmented() throws StorageException {
        return this.blobServiceClient.listContainersSegmented();
    }

    /**
     * Returns a result segment of an enumerable collection of containers whose names begin with the specified prefix
     * for the service client associated with this container.
     * 
     * @param prefix
     *            A <code>String</code> that represents the blob name prefix.
     * 
     * @return A {@link ResultSegment} object that contains a segment of the enumerable collection of
     *         {@link CloudBlobContainer} objects that represent the containers whose names begin with the specified
     *         prefix for the service client associated with this container.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ResultSegment<CloudBlobContainer> listContainersSegmented(final String prefix) throws StorageException {
        return this.blobServiceClient.listContainersSegmented(prefix);
    }

    /**
     * Returns a result segment containing a collection of containers whose names begin with
     * the specified prefix for the service client associated with this container,
     * using the specified listing details options, request options, and operation context.
     * 
     * @param prefix
     *            A <code>String</code> that represents the prefix of the container name.
     * @param detailsIncluded
     *            A {@link ContainerListingDetails} object that indicates whether metadata is included.
     * @param maxResults
     *            The maximum number of results to retrieve.  If <code>null</code> 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 BlobRequestOptions} object that specifies any additional options for the
     *            request. Specifying <code>null</code> will use the default request options from
     *            the associated service client ( {@link 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 A {@link ResultSegment} object that contains a segment of the enumerable collection
     *         of {@link CloudBlobContainer} objects that represent the containers whose names
     *         begin with the specified prefix for the service client associated with this container.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * 
     */
    @DoesServiceRequest
    public ResultSegment<CloudBlobContainer> listContainersSegmented(final String prefix,
            final ContainerListingDetails detailsIncluded, final Integer maxResults,
            final ResultContinuation continuationToken, final BlobRequestOptions options,
            final OperationContext opContext) throws StorageException {
        return this.blobServiceClient.listContainersSegmented(prefix, detailsIncluded, maxResults, continuationToken,
                options, opContext);
    }

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

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

        this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri);
        
        final StorageCredentialsSharedAccessSignature parsedCredentials = 
                SharedAccessSignatureHelper.parseQuery(completeUri);

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

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

    void updatePropertiesFromResponse(HttpURLConnection request) {
        // ETag
        this.getProperties().setEtag(request.getHeaderField(Constants.HeaderConstants.ETAG));

        // Last Modified
        if (0 != request.getLastModified()) {
            final Calendar lastModifiedCalendar = Calendar.getInstance(Utility.LOCALE_US);
            lastModifiedCalendar.setTimeZone(Utility.UTC_ZONE);
            lastModifiedCalendar.setTime(new Date(request.getLastModified()));
            this.getProperties().setLastModified(lastModifiedCalendar.getTime());
        }
    }

    /**
     * Sets the metadata collection of name-value pairs to be set on the container with an {@link #uploadMetadata} call.
     * This collection will overwrite any existing container metadata. If this is set to an empty collection, the
     * container metadata will be cleared on an {@link #uploadMetadata} call.
     * 
     * @param metadata
     *            A <code>java.util.HashMap</code> object that represents the metadata being assigned to the container.
     */
    public void setMetadata(final HashMap<String, String> metadata) {
        this.metadata = metadata;
    }

    /**
     * Sets the list of URIs for all locations.
     * 
     * @param storageUri
     *            A {@link StorageUri} object which represents the list of URIs for all locations.
     */
    protected void setStorageUri(final StorageUri storageUri) {
        this.storageUri = storageUri;
    }

    /**
     * Sets the properties for the container.
     * 
     * @param properties
     *            A {@link BlobContainerProperties} object that represents the properties being assigned to the
     *            container.
     */
    protected void setProperties(final BlobContainerProperties properties) {
        this.properties = properties;
    }

    /**
     * Uploads the container's metadata.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadMetadata() throws StorageException {
        this.uploadMetadata(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Uploads the container's metadata using the specified request options and operation context.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the container.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 uploadMetadata(AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.uploadMetadataImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext);

    }

    @DoesServiceRequest
    private StorageRequest<CloudBlobClient, CloudBlobContainer, Void> uploadMetadataImpl(
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlobContainer, Void> putRequest = new StorageRequest<CloudBlobClient, CloudBlobContainer, Void>(
                options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                    OperationContext context) throws Exception {
                return BlobRequest.setContainerMetadata(
                        container.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, accessCondition);
            }

            @Override
            public void setHeaders(HttpURLConnection connection, CloudBlobContainer container, OperationContext context) {
                BlobRequest.addMetadata(connection, container.metadata, context);
            }

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

            @Override
            public Void preProcessResponse(CloudBlobContainer container, CloudBlobClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                }

                container.updatePropertiesFromResponse(this.getConnection());
                return null;
            }
        };

        return putRequest;
    }

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

    /**
     * Uploads the container's permissions using the specified request options and operation context.
     * 
     * @param permissions
     *            A {@link BlobContainerPermissions} object that represents the permissions to upload.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the container.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 uploadPermissions(final BlobContainerPermissions permissions, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException {
        if (permissions.getPublicAccess() == BlobContainerPublicAccessType.UNKNOWN) {
            throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.ARGUMENT_OUT_OF_RANGE_ERROR, "accessType", permissions.getPublicAccess()));
        }

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

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

    private StorageRequest<CloudBlobClient, CloudBlobContainer, Void> uploadPermissionsImpl(
            final BlobContainerPermissions permissions, final AccessCondition accessCondition,
            final BlobRequestOptions options) throws StorageException {
        try {
            final StringWriter outBuffer = new StringWriter();
            SharedAccessPolicySerializer.writeSharedAccessIdentifiersToStream(permissions.getSharedAccessPolicies(),
                    outBuffer);
            final byte[] aclBytes = outBuffer.toString().getBytes(Constants.UTF8_CHARSET);
            final StorageRequest<CloudBlobClient, CloudBlobContainer, Void> putRequest = new StorageRequest<CloudBlobClient, CloudBlobContainer, Void>(
                    options, this.getStorageUri()) {
                @Override
                public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                        OperationContext context) throws Exception {
                    this.setSendStream(new ByteArrayInputStream(aclBytes));
                    this.setLength((long) aclBytes.length);
                    return BlobRequest.setAcl(container.getTransformedAddress().getUri(this.getCurrentLocation()),
                            options, context, accessCondition, permissions.getPublicAccess());
                }

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

                @Override
                public Void preProcessResponse(CloudBlobContainer container, CloudBlobClient client,
                        OperationContext context) throws Exception {
                    if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                        this.setNonExceptionedRetryableFailure(true);
                        return null;
                    }

                    container.updatePropertiesFromResponse(this.getConnection());
                    container.getProperties().setPublicAccess(permissions.getPublicAccess());
                    return null;
                }
            };

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

    /**
     * Acquires a new infinite lease on the container.
     *
     * @return A <code>String</code> that represents the lease ID.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final String acquireLease() throws StorageException {
        return this.acquireLease(null /* leaseTimeInSeconds */, null /* proposedLeaseId */);
    }

    /**
     * Acquires a new lease on the container with the specified lease time and proposed lease ID.
     * 
     * @param leaseTimeInSeconds
     *            An <code>Integer</code> which specifies the span of time for which to acquire the lease, in seconds.
     *            If null, an infinite lease will be acquired. If not null, the value must be greater than
     *            zero.
     * 
     * @param proposedLeaseId
     *            A <code>String</code> that represents the proposed lease ID for the new lease,
     *            or null if no lease ID is proposed.
     * 
     * @return A <code>String</code> that represents the lease ID.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final String acquireLease(final Integer leaseTimeInSeconds, final String proposedLeaseId)
            throws StorageException {
        return this.acquireLease(leaseTimeInSeconds, proposedLeaseId, null /* accessCondition */, null /* options */,
                null /* opContext */);
    }

    /**
     * Acquires a new lease on the container with the specified lease time, proposed lease ID, request
     * options, and operation context.
     * 
     * @param leaseTimeInSeconds
     *            An <code>Integer</code> which specifies the span of time for which to acquire the lease, in seconds.
     *            If null, an infinite lease will be acquired. If not null, the value must be greater than
     *            zero.
     * 
     * @param proposedLeaseId
     *            A <code>String</code> that represents the proposed lease ID for the new lease,
     *            or null if no lease ID is proposed.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the container.
     * 
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client
     *            ({@link CloudBlobClient}).
     * 
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. The context
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     * 
     * @return A <code>String</code> that represents the lease ID.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final String acquireLease(final Integer leaseTimeInSeconds, final String proposedLeaseId,
            final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.acquireLeaseImpl(leaseTimeInSeconds, proposedLeaseId, accessCondition, options),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlobContainer, String> acquireLeaseImpl(
            final Integer leaseTimeInSeconds, final String proposedLeaseId, final AccessCondition accessCondition,
            final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlobContainer, String> putRequest = new StorageRequest<CloudBlobClient, CloudBlobContainer, String>(
                options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                    OperationContext context) throws Exception {
                return BlobRequest.leaseContainer(container.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, accessCondition, LeaseAction.ACQUIRE, leaseTimeInSeconds, proposedLeaseId,
                        null /* breakPeriodInSeconds */);
            }

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

            @Override
            public String preProcessResponse(CloudBlobContainer container, CloudBlobClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                container.updatePropertiesFromResponse(this.getConnection());

                return BlobResponse.getLeaseID(this.getConnection());
            }

        };

        return putRequest;
    }

    /**
     * Renews an existing lease with the specified access conditions.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the container. The lease
     *            ID is required to be set with an access condition.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final void renewLease(final AccessCondition accessCondition) throws StorageException {
        this.renewLease(accessCondition, null /* options */, null /* opContext */);
    }

    /**
     * Renews an existing lease with the specified access conditions, request options, and operation context.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob. The lease ID is
     *            required to be set with an access condition.
     * 
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client
     *            ({@link CloudBlobClient}).
     * 
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. The context
     *            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 final void renewLease(final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        Utility.assertNotNull("accessCondition", accessCondition);
        Utility.assertNotNullOrEmpty("leaseID", accessCondition.getLeaseID());

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.renewLeaseImpl(accessCondition, options),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlobContainer, Void> renewLeaseImpl(
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlobContainer, Void> putRequest = new StorageRequest<CloudBlobClient, CloudBlobContainer, Void>(
                options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                    OperationContext context) throws Exception {
                return BlobRequest.leaseContainer(container.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, accessCondition, LeaseAction.RENEW, null /* leaseTimeInSeconds */,
                        null /* proposedLeaseId */, null /* breakPeriodInseconds */);
            }

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

            @Override
            public Void preProcessResponse(CloudBlobContainer container, CloudBlobClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                container.updatePropertiesFromResponse(this.getConnection());
                return null;
            }
        };

        return putRequest;
    }

    /**
     * Releases the lease on the container.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob. The lease ID is
     *            required to be set with an access condition.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final void releaseLease(final AccessCondition accessCondition) throws StorageException {
        this.releaseLease(accessCondition, null /* options */, null /* opContext */);
    }

    /**
     * Releases the lease on the container using the specified access conditions, request options, and operation
     * context.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob. The lease ID is
     *            required to be set with an access condition.
     * 
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client
     *            ({@link CloudBlobClient}).
     * 
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. The context
     *            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 final void releaseLease(final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        Utility.assertNotNull("accessCondition", accessCondition);
        Utility.assertNotNullOrEmpty("leaseID", accessCondition.getLeaseID());

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.releaseLeaseImpl(accessCondition, options),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlobContainer, Void> releaseLeaseImpl(
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlobContainer, Void> putRequest = new StorageRequest<CloudBlobClient, CloudBlobContainer, Void>(
                options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                    OperationContext context) throws Exception {
                return BlobRequest.leaseContainer(container.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, accessCondition, LeaseAction.RELEASE, null, null, null);
            }

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

            @Override
            public Void preProcessResponse(CloudBlobContainer container, CloudBlobClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                container.updatePropertiesFromResponse(this.getConnection());
                return null;
            }
        };

        return putRequest;
    }

    /**
     * Breaks the lease and ensures that another client cannot acquire a new lease until the current lease
     * period has expired.
     * 
     * @param breakPeriodInSeconds
     *            An <code>Integer</code> which specifies the time to wait, in seconds, until the current lease is
     *            broken.
     *            If null, the break period is the remainder of the current lease, or zero for infinite leases.
     * 
     * @return The time, in seconds, remaining in the lease period.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final long breakLease(final Integer breakPeriodInSeconds) throws StorageException {
        return this.breakLease(breakPeriodInSeconds, null /* accessCondition */, null, null);
    }

    /**
     * Breaks the existing lease, using the specified request options and operation context, and ensures that
     * another client cannot acquire a new lease until the current lease period has expired.
     * 
     * @param breakPeriodInSeconds
     *            An <code>Integer</code> which specifies the time to wait, in seconds, until the current lease is
     *            broken.
     *            If null, the break period is the remainder of the current lease, or zero for infinite leases.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client
     *            ({@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. The context
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     * 
     * @return The time, in seconds, remaining in the lease period.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final long breakLease(final Integer breakPeriodInSeconds, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        if (breakPeriodInSeconds != null) {
            Utility.assertGreaterThanOrEqual("breakPeriodInSeconds", breakPeriodInSeconds, 0);
        }

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.breakLeaseImpl(breakPeriodInSeconds, accessCondition, options), options.getRetryPolicyFactory(),
                opContext);
    }

    private final StorageRequest<CloudBlobClient, CloudBlobContainer, Long> breakLeaseImpl(
            final Integer breakPeriodInSeconds, final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlobContainer, Long> putCmd = new StorageRequest<CloudBlobClient, CloudBlobContainer, Long>(
                options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                    OperationContext context) throws Exception {
                return BlobRequest.leaseContainer(container.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, accessCondition, LeaseAction.BREAK, null, null, breakPeriodInSeconds);
            }

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

            @Override
            public Long preProcessResponse(CloudBlobContainer container, CloudBlobClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) {
                    this.setNonExceptionedRetryableFailure(true);
                    return -1L;
                }

                container.updatePropertiesFromResponse(this.getConnection());
                final String leaseTime = BlobResponse.getLeaseTime(this.getConnection());
                return Utility.isNullOrEmpty(leaseTime) ? -1L : Long.parseLong(leaseTime);
            }
        };

        return putCmd;
    }

    /**
     * Changes the existing lease ID to the proposed lease ID.
     * 
     * @param proposedLeaseId
     *            A <code>String</code> that represents the proposed lease ID for the new lease,
     *            or null if no lease ID is proposed.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob. The lease ID is
     *            required to be set with an access condition.
     * @return A <code>String</code> that represents the new lease ID.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final String changeLease(final String proposedLeaseId, final AccessCondition accessCondition)
            throws StorageException {
        return this.changeLease(proposedLeaseId, accessCondition, null, null);
    }

    /**
     * Changes the existing lease ID to the proposed lease Id with the specified access conditions, request options,
     * and operation context.
     * 
     * @param proposedLeaseId
     *            A <code>String</code> that represents the proposed lease ID for the new lease. This cannot be null.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob. The lease ID is
     *            required to be set with an access condition.
     * 
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client
     *            ({@link CloudBlobClient}).
     * 
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. The context
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     * @return A <code>String</code> that represents the new lease ID.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final String changeLease(final String proposedLeaseId, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException {
        Utility.assertNotNull("proposedLeaseId", proposedLeaseId);
        Utility.assertNotNull("accessCondition", accessCondition);
        Utility.assertNotNullOrEmpty("leaseID", accessCondition.getLeaseID());

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);

        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.changeLeaseImpl(proposedLeaseId, accessCondition, options), options.getRetryPolicyFactory(),
                opContext);
    }

    private final StorageRequest<CloudBlobClient, CloudBlobContainer, String> changeLeaseImpl(
            final String proposedLeaseId, final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlobContainer, String> putRequest = new StorageRequest<CloudBlobClient, CloudBlobContainer, String>(
                options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlobContainer container,
                    OperationContext context) throws Exception {
                return BlobRequest.leaseContainer(container.getTransformedAddress().getUri(this.getCurrentLocation()),
                        options, context, accessCondition, LeaseAction.CHANGE, null, proposedLeaseId, null);
            }

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

            @Override
            public String preProcessResponse(CloudBlobContainer container, CloudBlobClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                container.updatePropertiesFromResponse(this.getConnection());
                return BlobResponse.getLeaseID(this.getConnection());
            }
        };

        return putRequest;
    }
} public final class CloudBlockBlob extends CloudBlob {

    /**
     * Creates an instance of the <code>CloudBlockBlob</code> class using the specified absolute URI.
     * 
     * @param blobAbsoluteUri
     *            A <code>java.net.URI</code> object that represents the absolute URI to the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudBlockBlob(final URI blobAbsoluteUri) throws StorageException {
        this(new StorageUri(blobAbsoluteUri));
    }

    /**
     * Creates an instance of the <code>CloudBlockBlob</code> class using the specified absolute StorageUri.
     * 
     * @param blobAbsoluteUri
     *            A {@link StorageUri} object that represents the absolute URI to the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudBlockBlob(final StorageUri blobAbsoluteUri) throws StorageException {
        this(blobAbsoluteUri, (StorageCredentials)null);
    }

    /**
     * Creates an instance of the <code>CloudBlockBlob</code> class by copying values from another cloud block blob.
     * 
     * @param otherBlob
     *            A <code>CloudBlockBlob</code> object that represents the block blob to copy.
     */
    public CloudBlockBlob(final CloudBlockBlob otherBlob) {
        super(otherBlob);
    }

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

    /**
     * Creates an instance of the <code>CloudBlockBlob</code> class using the specified absolute StorageUri and credentials.
     * 
     * @param blobAbsoluteUri
     *            A {@link StorageUri} object that represents the absolute StorageUri to the blob.
     * @param credentials
     *            A {@link StorageCredentials} object used to authenticate access.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudBlockBlob(final StorageUri blobAbsoluteUri, final StorageCredentials credentials) throws StorageException {
        this(blobAbsoluteUri, null /* snapshotID */, credentials);
    }

    /**
     * Creates an instance of the <code>CloudBlockBlob</code> class using the specified absolute URI, snapshot ID, and
     * credentials.
     * 
     * @param blobAbsoluteUri
     *            A <code>java.net.URI</code> object that represents the absolute URI to the blob.
     * @param snapshotID
     *            A <code>String</code> that represents the snapshot version, if applicable.
     * @param credentials
     *            A {@link StorageCredentials} object used to authenticate access.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudBlockBlob(final URI blobAbsoluteUri, final String snapshotID, final StorageCredentials credentials)
            throws StorageException {
        this(new StorageUri(blobAbsoluteUri), snapshotID, credentials);
    }

    /**
     * Creates an instance of the <code>CloudBlockBlob</code> class using the specified absolute StorageUri, snapshot
     * ID, and credentials.
     * 
     * @param blobAbsoluteUri
     *            A {@link StorageUri} object that represents the absolute StorageUri to the blob.
     * @param snapshotID
     *            A <code>String</code> that represents the snapshot version, if applicable.
     * @param credentials
     *            A {@link StorageCredentials} object used to authenticate access.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudBlockBlob(final StorageUri blobAbsoluteUri, final String snapshotID, final StorageCredentials credentials)
            throws StorageException {
        super(BlobType.BLOCK_BLOB, blobAbsoluteUri, snapshotID, credentials);
    }
    
    /**
     * Creates an instance of the <code>CloudBlockBlob</code> class using the specified type, name, snapshot ID, and
     * container.
     *
     * @param blobName
     *            Name of the blob.
     * @param snapshotID
     *            A <code>String</code> that represents the snapshot version, if applicable.
     * @param container
     *            The reference to the parent container.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    protected CloudBlockBlob(String blobName, String snapshotID, CloudBlobContainer container)
            throws URISyntaxException {
        super(BlobType.BLOCK_BLOB, blobName, snapshotID, container);
    }
    
    /**
     * Requests the service to start copying a block blob's contents, properties, and metadata to a new block blob.
     *
     * @param sourceBlob
     *            A <code>CloudBlockBlob</code> object that represents the source blob to copy.
     *
     * @return A <code>String</code> which represents the copy ID associated with the copy operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     */
    @DoesServiceRequest
    public final String startCopy(final CloudBlockBlob sourceBlob) throws StorageException, URISyntaxException {
        return this.startCopy(sourceBlob, null /* sourceAccessCondition */,
                null /* destinationAccessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Requests the service to start copying a block blob's contents, properties, and metadata to a new block blob,
     * using the specified access conditions, lease ID, request options, and operation context.
     *
     * @param sourceBlob
     *            A <code>CloudBlockBlob</code> object that represents the source blob to copy.
     * @param sourceAccessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the source blob.
     * @param destinationAccessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the destination blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 A <code>String</code> which represents the copy ID associated with the copy operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *
     */
    @DoesServiceRequest
    public final String startCopy(final CloudBlockBlob sourceBlob, final AccessCondition sourceAccessCondition,
            final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException, URISyntaxException {
        Utility.assertNotNull("sourceBlob", sourceBlob);

        URI source = sourceBlob.getSnapshotQualifiedUri();
        if (sourceBlob.getServiceClient() != null && sourceBlob.getServiceClient().getCredentials() != null)
        {
            source = sourceBlob.getServiceClient().getCredentials().transformUri(sourceBlob.getSnapshotQualifiedUri());
        }

        return this.startCopy(source, sourceAccessCondition, destinationAccessCondition, options, opContext);
    }

    /**
    * Requests the service to start copying a file's contents, properties, and metadata to a new block blob.
    *
    * @param sourceFile
    *             A <code>CloudFile</code> object that represents the source file to copy.
    *
    * @return A <code>String</code> which represents the copy ID associated with the copy operation.
    *
    * @throws StorageException
    *             If a storage service error occurred.
    * @throws URISyntaxException
    */
   @DoesServiceRequest
   public final String startCopy(final CloudFile sourceFile) throws StorageException, URISyntaxException {
       return this.startCopy(sourceFile, null /* sourceAccessCondition */,
               null /* destinationAccessCondition */, null /* options */, null /* opContext */);
   }

   /**
    * Requests the service to start copying a file's contents, properties, and metadata to a new block blob,
    * using the specified access conditions, lease ID, request options, and operation context.
    *
    * @param sourceFile
    *            A <code>CloudFile</code> object that represents the source file to copy.
    * @param sourceAccessCondition
    *            An {@link AccessCondition} object that represents the access conditions for the source file.
    * @param destinationAccessCondition
    *            An {@link AccessCondition} object that represents the access conditions for the destination block blob.
    * @param options
    *            A {@link BlobRequestOptions} object that specifies any additional options for the request.
    *            Specifying <code>null</code> will use the default request options from the associated
    *            service client ({@link 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 A <code>String</code> which represents the copy ID associated with the copy operation.
    *
    * @throws StorageException
    *             If a storage service error occurred.
    * @throws URISyntaxException
    *             If the resource URI is invalid.
    */
   @DoesServiceRequest
   public final String startCopy(final CloudFile sourceFile, final AccessCondition sourceAccessCondition,
           final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext)
           throws StorageException, URISyntaxException {
       Utility.assertNotNull("sourceFile", sourceFile);
       return this.startCopy(
               sourceFile.getServiceClient().getCredentials().transformUri(sourceFile.getUri()),
               sourceAccessCondition, destinationAccessCondition, options, opContext);
   }

    /**
     * Commits a block list to the storage service. In order to be written as part of a blob, a block must have been
     * successfully written to the server in a prior uploadBlock operation.
     * 
     * @param blockList
     *            An enumerable collection of {@link BlockEntry} objects that represents the list block items being
     *            committed. The <code>size</code> field is ignored.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void commitBlockList(final Iterable<BlockEntry> blockList) throws StorageException {
        this.commitBlockList(blockList, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Commits a block list to the storage service using the specified lease ID, request options, and operation context.
     * In order to be written as part of a blob, a block must have been successfully written to the server in a prior
     * uploadBlock operation.
     * 
     * @param blockList
     *            An enumerable collection of {@link BlockEntry} objects that represents the list block items being
     *            committed. The size field is ignored.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 commitBlockList(final Iterable<BlockEntry> blockList, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException {
        assertNoWriteOperationForSnapshot();

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

        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient);

        ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.commitBlockListImpl(blockList, accessCondition, options, opContext),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlob, Void> commitBlockListImpl(final Iterable<BlockEntry> blockList,
            final AccessCondition accessCondition, final BlobRequestOptions options, final OperationContext opContext)
            throws StorageException {

        byte[] blockListBytes;
        try {
            blockListBytes = BlockEntryListSerializer.writeBlockListToStream(blockList, opContext);

            final ByteArrayInputStream blockListInputStream = new ByteArrayInputStream(blockListBytes);

            // This also marks the stream. Therefore no need to mark it in buildRequest.
            final StreamMd5AndLength descriptor = Utility.analyzeStream(blockListInputStream, -1L, -1L,
                    true /* rewindSourceStream */, options.getUseTransactionalContentMD5() /* calculateMD5 */);

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

                @Override
                public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
                        throws Exception {
                    this.setSendStream(blockListInputStream);
                    this.setLength(descriptor.getLength());
                    return BlobRequest.putBlockList(
                            blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context,
                            accessCondition, blob.properties);
                }

                @Override
                public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationContext context) {
                    BlobRequest.addMetadata(connection, blob.metadata, context);

                    if (options.getUseTransactionalContentMD5()) {
                        connection.setRequestProperty(Constants.HeaderConstants.CONTENT_MD5, descriptor.getMd5());
                    }
                }

                @Override
                public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
                        throws Exception {
                    StorageRequest.signBlobQueueAndFileRequest(connection, client, this.getLength(), context);
                }

                @Override
                public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
                        throws Exception {
                    if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) {
                        this.setNonExceptionedRetryableFailure(true);
                        return null;
                    }

                    blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
                    this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection()));
                    return null;
                }

                @Override
                public void recoveryAction(OperationContext context) throws IOException {
                    blockListInputStream.reset();
                    blockListInputStream.mark(Constants.MAX_MARK_LENGTH);
                }
            };

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

    /**
     * Downloads the committed block list from the block blob.
     * <p>
     * The committed block list includes the list of blocks that have been successfully committed to the block blob. The
     * list of committed blocks is returned in the same order that they were committed to the blob. No block may appear
     * more than once in the committed block list.
     * 
     * @return An <code>ArrayList</code> object of {@link BlockEntry} objects that represent the committed list
     *         block items downloaded from the block blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ArrayList<BlockEntry> downloadBlockList() throws StorageException {
        return this.downloadBlockList(BlockListingFilter.COMMITTED, null /* accessCondition */, null /* options */,
                null /* opContext */);
    }

    /**
     * Downloads the block list from the block blob using the specified block listing filter, request options, and
     * operation context.
     * <p>
     * The committed block list includes the list of blocks that have been successfully committed to the block blob. The
     * list of committed blocks is returned in the same order that they were committed to the blob. No block may appear
     * more than once in the committed block list.
     * 
     * @param blockListingFilter
     *            A {@link BlockListingFilter} value that specifies whether to download committed blocks, uncommitted
     *            blocks, or all blocks.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 An <code>ArrayList</code> object of {@link BlockEntry} objects that represent the list block items
     *         downloaded from the block blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ArrayList<BlockEntry> downloadBlockList(final BlockListingFilter blockListingFilter,
            final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        Utility.assertNotNull("blockListingFilter", blockListingFilter);

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient);

        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.downloadBlockListImpl(blockListingFilter, accessCondition, options),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlob, ArrayList<BlockEntry>> downloadBlockListImpl(
            final BlockListingFilter blockListingFilter, final AccessCondition accessCondition,
            final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlob, ArrayList<BlockEntry>> getRequest = new StorageRequest<CloudBlobClient, CloudBlob, ArrayList<BlockEntry>>(
                options, this.getStorageUri()) {

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

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
                    throws Exception {
                return BlobRequest.getBlockList(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
                        options, context, accessCondition, blob.snapshotID, blockListingFilter);
            }

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

            @Override
            public ArrayList<BlockEntry> preProcessResponse(CloudBlob blob, CloudBlobClient client,
                    OperationContext context) throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                }

                return null;
            }

            @Override
            public ArrayList<BlockEntry> postProcessResponse(HttpURLConnection connection, CloudBlob blob,
                    CloudBlobClient client, OperationContext context, ArrayList<BlockEntry> storageObject)
                    throws Exception {
                blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
                blob.updateLengthFromResponse(this.getConnection());

                return BlockListHandler.getBlockList(this.getConnection().getInputStream());
            }
        };

        return getRequest;
    }

    /**
     * Creates and opens an output stream to write data to the block blob. If the blob already exists on the service, it
     * will be overwritten.
     * <p>
     * To avoid overwriting and instead throw an error, please use the 
     * {@link #openOutputStream(AccessCondition, BlobRequestOptions, OperationContext)} overload with the appropriate 
     * {@link AccessCondition}.
     * 
     * @return A {@link BlobOutputStream} object used to write data to the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public BlobOutputStream openOutputStream() throws StorageException {
        return this.openOutputStream(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Creates and opens an output stream to write data to the block blob using the specified request options and
     * operation context. If the blob already exists on the service, it will be overwritten.
     * <p>
     * To avoid overwriting and instead throw an error, please pass in an {@link AccessCondition} generated using 
     * {@link AccessCondition#generateIfNotExistsCondition()}.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 A {@link BlobOutputStream} object used to write data to the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public BlobOutputStream openOutputStream(AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        assertNoWriteOperationForSnapshot();

        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient, 
                false /* setStartTime */);
        
        // TODO: Apply any conditional access conditions up front

        return new BlobOutputStream(this, accessCondition, options, opContext);
    }

    /**
     * Uploads the source stream data to the block blob. If the blob already exists on the service, it will be 
     * overwritten.
     * 
     * @param sourceStream
     *            An {@link InputStream} object that represents the input stream to write to the block blob.
     * @param length
     *            A <code>long</code> which represents the length, in bytes, of the stream data, or -1 if unknown.
     * 
     * @throws IOException
     *             If an I/O error occurred.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @Override
    @DoesServiceRequest
    public void upload(final InputStream sourceStream, final long length) throws StorageException, IOException {
        this.upload(sourceStream, length, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Uploads the source stream data to the blob, using the specified lease ID, request options, and operation context.
     * If the blob already exists on the service, it will be overwritten.
     * 
     * @param sourceStream
     *            An {@link InputStream} object that represents the input stream to write to the block blob.
     * @param length
     *            A <code>long</code> which represents the length, in bytes, of the stream data, or -1 if unknown.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 IOException
     *             If an I/O error occurred.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @Override
    @DoesServiceRequest
    public void upload(final InputStream sourceStream, final long length, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException {
        if (length < -1) {
            throw new IllegalArgumentException(SR.STREAM_LENGTH_NEGATIVE);
        }

        assertNoWriteOperationForSnapshot();

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient);

        StreamMd5AndLength descriptor = new StreamMd5AndLength();
        descriptor.setLength(length);

        if (sourceStream.markSupported()) {
            // Mark sourceStream for current position.
            sourceStream.mark(Constants.MAX_MARK_LENGTH);
        }

        // If the stream is rewindable and the length is unknown or we need to
        // set md5, then analyze the stream.
        // Note this read will abort at
        // options.getSingleBlobPutThresholdInBytes() bytes and return
        // -1 as length in which case we will revert to using a stream as it is
        // over the single put threshold.
        if (sourceStream.markSupported()
                && (length < 0 || (options.getStoreBlobContentMD5() && length <= options
                        .getSingleBlobPutThresholdInBytes()))) {
            // If the stream is of unknown length or we need to calculate
            // the MD5, then we we need to read the stream contents first

            descriptor = Utility.analyzeStream(sourceStream, length, options.getSingleBlobPutThresholdInBytes() + 1,
                    true /* rewindSourceStream */, options.getStoreBlobContentMD5());

            if (descriptor.getMd5() != null && options.getStoreBlobContentMD5()) {
                this.properties.setContentMD5(descriptor.getMd5());
            }
        }

        // If the stream is rewindable, and the length is known and less than
        // threshold the upload in a single put, otherwise use a stream.
        if (sourceStream.markSupported() && descriptor.getLength() != -1
                && descriptor.getLength() < options.getSingleBlobPutThresholdInBytes() + 1) {
            this.uploadFullBlob(sourceStream, descriptor.getLength(), accessCondition, options, opContext);
        }
        else {
            final BlobOutputStream writeStream = this.openOutputStream(accessCondition, options, opContext);
            try {
                writeStream.write(sourceStream, length);
            }
            finally {
                writeStream.close();
            }
        }
    }

    /**
     * Uploads a blob in a single operation.
     *
     * @param sourceStream
     *            A <code>InputStream</code> object that represents the source stream to upload.
     * @param length
     *            The length, in bytes, of the stream, or -1 if unknown.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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
    protected final void uploadFullBlob(final InputStream sourceStream, final long length,
            final AccessCondition accessCondition, final BlobRequestOptions options, final OperationContext opContext)
            throws StorageException {
        assertNoWriteOperationForSnapshot();

        // Mark sourceStream for current position.
        sourceStream.mark(Constants.MAX_MARK_LENGTH);

        if (length < 0 || length > BlobConstants.MAX_SINGLE_UPLOAD_BLOB_SIZE_IN_BYTES) {
            throw new IllegalArgumentException(String.format(SR.INVALID_STREAM_LENGTH,
                    BlobConstants.MAX_SINGLE_UPLOAD_BLOB_SIZE_IN_BYTES / Constants.MB));
        }

        ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                uploadFullBlobImpl(sourceStream, length, accessCondition, options, opContext),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlob, Void> uploadFullBlobImpl(final InputStream sourceStream,
            final long length, final AccessCondition accessCondition, final BlobRequestOptions options,
            final OperationContext opContext) {
        final StorageRequest<CloudBlobClient, CloudBlob, Void> putRequest = new StorageRequest<CloudBlobClient, CloudBlob, Void>(
                options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
                    throws Exception {
                this.setSendStream(sourceStream);
                this.setLength(length);
                return BlobRequest.putBlob(blob.getTransformedAddress(opContext).getUri(this.getCurrentLocation()),
                        options, opContext, accessCondition, blob.properties, blob.properties.getBlobType(),
                        this.getLength());
            }

            @Override
            public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationContext context) {
                BlobRequest.addMetadata(connection, blob.metadata, opContext);
            }

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

            @Override
            public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
                this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection()));
                return null;
            }

            @Override
            public void recoveryAction(OperationContext context) throws IOException {
                sourceStream.reset();
                sourceStream.mark(Constants.MAX_MARK_LENGTH);
            }

            @Override
            public void validateStreamWrite(StreamMd5AndLength descriptor) throws StorageException {
                if (this.getLength() != null && this.getLength() != -1) {
                    if (length != descriptor.getLength()) {
                        throw new StorageException(StorageErrorCodeStrings.INVALID_INPUT, SR.INCORRECT_STREAM_LENGTH,
                                HttpURLConnection.HTTP_FORBIDDEN, null, null);
                    }
                }
            }
        };

        return putRequest;
    }
    
    /**
     * Uploads a block to be committed as part of the block blob, using the specified block ID.
     * 
     * @param blockId
     *            A <code>String</code> that represents the Base-64 encoded block ID. Note for a given blob the length
     *            of all Block IDs must be identical.
     * @param sourceStream
     *            An {@link InputStream} object that represents the input stream to write to the block blob.
     * @param length
     *            A <code>long</code> which represents the length, in bytes, of the stream data, or -1 if unknown.
     * @throws IOException
     *             If an I/O error occurred.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadBlock(final String blockId, final InputStream sourceStream, final long length)
            throws StorageException, IOException {
        this.uploadBlock(blockId, sourceStream, length, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Uploads a block to be committed as part of the block blob, using the specified block ID, the specified lease ID,
     * request options, and operation context.
     * 
     * @param blockId
     *            A <code>String</code> that represents the Base-64 encoded block ID. Note for a given blob the length
     *            of all Block IDs must be identical.
     * @param sourceStream
     *            An {@link InputStream} object that represents the input stream to write to the block blob.
     * @param length
     *            A <code>long</code> which represents the length, in bytes, of the stream data, or -1 if unknown.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 IOException
     *             If an I/O error occurred.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadBlock(final String blockId, final InputStream sourceStream, final long length,
            final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException, IOException {
        if (length < -1) {
            throw new IllegalArgumentException(SR.STREAM_LENGTH_NEGATIVE);
        }

        assertNoWriteOperationForSnapshot();

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

        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient);

        // Assert block length
        if (Utility.isNullOrEmpty(blockId) || !Base64.validateIsBase64String(blockId)) {
            throw new IllegalArgumentException(SR.INVALID_BLOCK_ID);
        }

        if (sourceStream.markSupported()) {
            // Mark sourceStream for current position.
            sourceStream.mark(Constants.MAX_MARK_LENGTH);
        }

        InputStream bufferedStreamReference = sourceStream;
        StreamMd5AndLength descriptor = new StreamMd5AndLength();
        descriptor.setLength(length);

        if (!sourceStream.markSupported()) {
            // needs buffering
            final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            descriptor = Utility.writeToOutputStream(sourceStream, byteStream, length, false /* rewindSourceStream */,
                    options.getUseTransactionalContentMD5(), opContext, options);

            bufferedStreamReference = new ByteArrayInputStream(byteStream.toByteArray());
        }
        else if (length < 0 || options.getUseTransactionalContentMD5()) {
            // If the stream is of unknown length or we need to calculate the
            // MD5, then we we need to read the stream contents first
            descriptor = Utility.analyzeStream(sourceStream, length, -1L, true /* rewindSourceStream */,
                    options.getUseTransactionalContentMD5());
        }

        if (descriptor.getLength() > 4 * Constants.MB) {
            throw new IllegalArgumentException(SR.STREAM_LENGTH_GREATER_THAN_4MB);
        }

        this.uploadBlockInternal(blockId, descriptor.getMd5(), bufferedStreamReference, descriptor.getLength(),
                accessCondition, options, opContext);
    }

    /**
     * Uploads a block of the blob to the server.
     * 
     * @param blockId
     *            A <code>String</code> which represents the Base64-encoded Block ID.
     * @param md5
     *            A <code>String</code> which represents the MD5 to use if it is set.
     * @param sourceStream
     *            An {@link InputStream} object to read from.
     * @param length
     *            A <code>long</code> which represents the length, in bytes, of the stream data, or -1 if unknown.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request.
     * @param opContext
     *            An {@link OperationContext} object that is used to track the execution of the operation.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    private void uploadBlockInternal(final String blockId, final String md5, final InputStream sourceStream,
            final long length, final AccessCondition accessCondition, final BlobRequestOptions options,
            final OperationContext opContext) throws StorageException {
        ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                uploadBlockImpl(blockId, md5, sourceStream, length, accessCondition, options, opContext),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlob, Void> uploadBlockImpl(final String blockId, final String md5,
            final InputStream sourceStream, final long length, final AccessCondition accessCondition,
            final BlobRequestOptions options, final OperationContext opContext) {

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

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
                    throws Exception {
                this.setSendStream(sourceStream);
                this.setLength(length);
                return BlobRequest.putBlock(blob.getTransformedAddress(opContext).getUri(this.getCurrentLocation()),
                        options, opContext, accessCondition, blockId);
            }

            @Override
            public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationContext context) {
                if (options.getUseTransactionalContentMD5()) {
                    connection.setRequestProperty(Constants.HeaderConstants.CONTENT_MD5, md5);
                }
            }

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

            @Override
            public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection()));
                return null;
            }

            @Override
            public void recoveryAction(OperationContext context) throws IOException {
                sourceStream.reset();
                sourceStream.mark(Constants.MAX_MARK_LENGTH);
            }
        };

        return putRequest;
    }

    /**
     * Uploads a blob from a string using the platform's default encoding. If the blob already exists on the service, it
     * will be overwritten.
     * 
     * @param content
     *            A <code>String</code> which represents the content that will be uploaded to the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws IOException
     */
    public void uploadText(final String content) throws StorageException, IOException {
        this.uploadText(content, null /* charsetName */, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Uploads a blob from a string using the specified encoding. If the blob already exists on the service, it will be 
     * overwritten.
     * 
     * @param content
     *            A <code>String</code> which represents the content that will be uploaded to the blob.
     * @param charsetName
     *            A <code>String</code> which represents the name of the charset to use to encode the content.
     *            If null, the platform's default encoding is used.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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.
     * @throws IOException
     */
    public void uploadText(final String content, final String charsetName, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException {
        byte[] bytes = (charsetName == null) ? content.getBytes() : content.getBytes(charsetName);
        this.uploadFromByteArray(bytes, 0, bytes.length, accessCondition, options, opContext);
    }

    /**
     * Downloads a blob to a string using the platform's default encoding.
     * 
     * @return A <code>String</code> which represents the blob's contents.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws IOException
     */
    public String downloadText() throws StorageException, IOException {
        return this
                .downloadText(null /* charsetName */, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Downloads a blob to a string using the specified encoding.
     * 
     * @param charsetName
     *            A <code>String</code> which represents the name of the charset to use to encode the content.
     *            If null, the platform's default encoding is used.
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link 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 A <code>String</code> which represents the blob's contents.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws IOException
     */
    public String downloadText(final String charsetName, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        this.download(baos, accessCondition, options, opContext);
        return charsetName == null ? baos.toString() : baos.toString(charsetName);
    }

    /**
     * Sets the number of bytes to buffer when writing to a {@link BlobOutputStream}.
     * 
     * @param streamWriteSizeInBytes
     *            An <code>int</code> which represents the maximum block size, in bytes, for writing to a block blob
     *            while using a {@link BlobOutputStream} object, ranging from 16 KB to 4 MB, inclusive.
     * 
     * @throws IllegalArgumentException
     *             If <code>streamWriteSizeInBytes</code> is less than 16 KB or greater than 4 MB.
     */
    @Override
    public void setStreamWriteSizeInBytes(final int streamWriteSizeInBytes) {
        if (streamWriteSizeInBytes > Constants.MAX_BLOCK_SIZE || streamWriteSizeInBytes < 16 * Constants.KB) {
            throw new IllegalArgumentException("StreamWriteSizeInBytes");
        }

        this.streamWriteSizeInBytes = streamWriteSizeInBytes;
    }

    /**
     * Sets the blob tier on a block blob on a standard storage account.
     * @param standardBlobTier
     *            A {@link StandardBlobTier} object which represents the tier of the blob.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadStandardBlobTier(final StandardBlobTier standardBlobTier) throws StorageException {
        this.uploadStandardBlobTier(standardBlobTier, null /* options */, null /* opContext */);
    }

    /**
     * Sets the tier on a block blob on a standard storage account.
     * @param standardBlobTier
     *            A {@link StandardBlobTier} object which represents the tier of the blob.
     * @param options
     *            A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
     *            <code>null</code> will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object which 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 uploadStandardBlobTier(final StandardBlobTier standardBlobTier, BlobRequestOptions options,
                                   OperationContext opContext) throws StorageException {
        assertNoWriteOperationForSnapshot();
        Utility.assertNotNull("standardBlobTier", standardBlobTier);

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

        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient);

        ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.uploadBlobTierImpl(standardBlobTier.toString(), options), options.getRetryPolicyFactory(), opContext);
    }
} public class SR {
    public static final String ACCOUNT_NAME_NULL_OR_EMPTY = "The account name is null or empty.";
    public static final String ACCOUNT_NAME_MISMATCH = "The account name does not match the existing account name on the credentials.";
    public static final String APPEND_BLOB_MD5_NOT_POSSIBLE = "MD5 cannot be calculated for an existing append blob because it would require reading the existing data. Please disable StoreFileContentMD5.";
    public static final String ARGUMENT_NULL_OR_EMPTY = "The argument must not be null or an empty string. Argument name: %s.";
    public static final String ARGUMENT_OUT_OF_RANGE_ERROR = "The argument is out of range. Argument name: %s, Value passed: %s.";
    public static final String ATTEMPTED_TO_SERIALIZE_INACCESSIBLE_PROPERTY = "An attempt was made to access an inaccessible member of the entity during serialization.";
    public static final String BLOB = "blob";
    public static final String BLOB_DATA_CORRUPTED = "Blob data corrupted (integrity check failed), Expected value is %s, retrieved %s";
    public static final String BLOB_ENDPOINT_NOT_CONFIGURED = "No blob endpoint configured.";
    public static final String BLOB_HASH_MISMATCH = "Blob hash mismatch (integrity check failed), Expected value is %s, retrieved %s.";
    public static final String BLOB_MD5_NOT_SUPPORTED_FOR_PAGE_BLOBS = "Blob level MD5 is not supported for page blobs.";
    public static final String BLOB_TYPE_NOT_DEFINED = "The blob type is not defined.  Allowed types are BlobType.BLOCK_BLOB and BlobType.Page_BLOB.";
    public static final String CANNOT_CREATE_SAS_FOR_GIVEN_CREDENTIALS = "Cannot create Shared Access Signature as the credentials does not have account name information. Please check that the credentials provided support creating Shared Access Signature.";
    public static final String CANNOT_CREATE_SAS_FOR_SNAPSHOTS = "Cannot create Shared Access Signature via references to blob snapshots. Please perform the given operation on the root blob instead.";
    public static final String CANNOT_CREATE_SAS_WITHOUT_ACCOUNT_KEY = "Cannot create Shared Access Signature unless the Account Key credentials are used by the ServiceClient.";
    public static final String CANNOT_TRANSFORM_NON_HTTPS_URI_WITH_HTTPS_ONLY_CREDENTIALS = "Cannot use HTTP with credentials that only support HTTPS.";
    public static final String CONTAINER = "container";
    public static final String CONTENT_LENGTH_MISMATCH = "An incorrect number of bytes was read from the connection. The connection may have been closed.";
    public static final String CREATING_NETWORK_STREAM = "Creating a NetworkInputStream and expecting to read %s bytes.";
    public static final String CREDENTIALS_CANNOT_SIGN_REQUEST = "CloudBlobClient, CloudQueueClient and CloudTableClient require credentials that can sign a request.";
    public static final String CUSTOM_RESOLVER_THREW = "The custom property resolver delegate threw an exception. Check the inner exception for more details.";
    public static final String DEFAULT_SERVICE_VERSION_ONLY_SET_FOR_BLOB_SERVICE = "DefaultServiceVersion can only be set for the Blob service.";
    public static final String DELETE_SNAPSHOT_NOT_VALID_ERROR = "The option '%s' must be 'None' to delete a specific snapshot specified by '%s'.";
    public static final String DIRECTORY = "directory";
    public static final String EDMTYPE_WAS_NULL = "EdmType cannot be null.";
    public static final String ENUMERATION_ERROR = "An error occurred while enumerating the result, check the original exception for details.";
    public static final String EMPTY_BATCH_NOT_ALLOWED = "Cannot execute an empty batch operation.";
    public static final String ENDPOINT_INFORMATION_UNAVAILABLE = "Endpoint information not available for Account using Shared Access Credentials.";
    public static final String ENTITY_PROPERTY_CANNOT_BE_NULL_FOR_PRIMITIVES = "EntityProperty cannot be set to null for primitive value types.";
    public static final String ETAG_INVALID_FOR_DELETE = "Delete requires a valid ETag (which may be the '*' wildcard).";
    public static final String ETAG_INVALID_FOR_MERGE = "Merge requires a valid ETag (which may be the '*' wildcard).";
    public static final String ETAG_INVALID_FOR_UPDATE = "Replace requires a valid ETag (which may be the '*' wildcard).";
    public static final String ENUM_COULD_NOT_BE_PARSED = "%s could not be parsed from '%s'.";
    public static final String EXCEPTION_THROWN_DURING_DESERIALIZATION = "The entity threw an exception during deserialization.";
    public static final String EXCEPTION_THROWN_DURING_SERIALIZATION = "The entity threw an exception during serialization.";
    public static final String EXPECTED_A_FIELD_NAME = "Expected a field name.";
    public static final String EXPECTED_END_ARRAY = "Expected the end of a JSON Array.";
    public static final String EXPECTED_END_OBJECT = "Expected the end of a JSON Object.";
    public static final String EXPECTED_START_ARRAY = "Expected the start of a JSON Array.";
    public static final String EXPECTED_START_ELEMENT_TO_EQUAL_ERROR = "Expected START_ELEMENT to equal error.";
    public static final String EXPECTED_START_OBJECT = "Expected the start of a JSON Object.";
    public static final String FAILED_TO_PARSE_PROPERTY = "Failed to parse property '%s' with value '%s' as type '%s'";
    public static final String FILE = "file";
    public static final String FILE_ENDPOINT_NOT_CONFIGURED = "No file endpoint configured.";
    public static final String FILE_HASH_MISMATCH = "File hash mismatch (integrity check failed), Expected value is %s, retrieved %s.";
    public static final String FILE_MD5_NOT_POSSIBLE = "MD5 cannot be calculated for an existing file because it would require reading the existing data. Please disable StoreFileContentMD5.";
    public static final String INCORRECT_STREAM_LENGTH = "An incorrect stream length was specified, resulting in an authentication failure. Please specify correct length, or -1.";
    public static final String INPUT_STREAM_SHOULD_BE_MARKABLE = "Input stream must be markable.";
    public static final String INVALID_ACCOUNT_NAME = "Invalid account name.";
    public static final String INVALID_ACL_ACCESS_TYPE = "Invalid acl public access type returned '%s'. Expected blob or container.";
    public static final String INVALID_BLOB_TYPE = "Incorrect Blob type, please use the correct Blob type to access a blob on the server. Expected %s, actual %s.";
    public static final String INVALID_BLOCK_ID = "Invalid blockID, blockID must be a valid Base64 String.";
    public static final String INVALID_BLOCK_SIZE = "Append block data should not exceed the maximum blob size condition value.";
    public static final String INVALID_CONDITIONAL_HEADERS = "The conditionals specified for this operation did not match server.";
    public static final String INVALID_CONNECTION_STRING = "Invalid connection string.";
    public static final String INVALID_CONNECTION_STRING_DEV_STORE_NOT_TRUE = "Invalid connection string, the UseDevelopmentStorage key must always be paired with 'true'.  Remove the flag entirely otherwise.";
    public static final String INVALID_CONTENT_LENGTH = "ContentLength must be set to -1 or positive Long value.";
    public static final String INVALID_CONTENT_TYPE = "An incorrect Content-Type was returned from the server.";
    public static final String INVALID_CORS_RULE = "A CORS rule must contain at least one allowed origin and allowed method, and MaxAgeInSeconds cannot have a value less than zero.";
    public static final String INVALID_DATE_STRING = "Invalid Date String: %s.";
    public static final String INVALID_EDMTYPE_VALUE = "Invalid value '%s' for EdmType.";
    public static final String INVALID_FILE_LENGTH = "File length must be greater than or equal to 0 bytes.";
    public static final String INVALID_GEO_REPLICATION_STATUS = "Null or Invalid geo-replication status in response: %s.";
    public static final String INVALID_IP_ADDRESS = "Error when parsing IPv4 address: IP address '%s' is invalid.";
    public static final String INVALID_KEY = "Storage Key is not a valid base64 encoded string.";
    public static final String INVALID_LISTING_DETAILS = "Invalid blob listing details specified.";
    public static final String INVALID_LOGGING_LEVEL = "Invalid logging operations specified.";
    public static final String INVALID_MAX_WRITE_SIZE = "Max write size is 4MB. Please specify a smaller range.";
    public static final String INVALID_MESSAGE_LENGTH = "The message size cannot be larger than %s bytes.";
    public static final String INVALID_MIME_RESPONSE = "Invalid MIME response received.";
    public static final String INVALID_NUMBER_OF_BYTES_IN_THE_BUFFER = "Page data must be a multiple of 512 bytes. Buffer currently contains %d bytes.";
    public static final String INVALID_OPERATION_FOR_A_SNAPSHOT = "Cannot perform this operation on a blob representing a snapshot.";
    public static final String INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT = "Cannot perform this operation on a share representing a snapshot.";
    public static final String INVALID_PAGE_BLOB_LENGTH = "Page blob length must be multiple of 512.";
    public static final String INVALID_PAGE_START_OFFSET = "Page start offset must be multiple of 512.";
    public static final String INVALID_RANGE_CONTENT_MD5_HEADER = "Cannot specify x-ms-range-get-content-md5 header on ranges larger than 4 MB. Either use a BlobReadStream via openRead, or disable TransactionalMD5 via the BlobRequestOptions.";
    public static final String INVALID_RESOURCE_NAME = "Invalid %s name. Check MSDN for more information about valid naming.";
    public static final String INVALID_RESOURCE_NAME_LENGTH = "Invalid %s name length. The name must be between %s and %s characters long.";
    public static final String INVALID_RESOURCE_RESERVED_NAME = "Invalid %s name. This name is reserved.";
    public static final String INVALID_RESPONSE_RECEIVED = "The response received is invalid or improperly formatted.";
    public static final String INVALID_STORAGE_PROTOCOL_VERSION = "Storage protocol version prior to 2009-09-19 do not support shared key authentication.";
    public static final String INVALID_STORAGE_SERVICE = "Invalid storage service specified.";
    public static final String INVALID_STREAM_LENGTH = "Invalid stream length; stream must be between 0 and %s MB in length.";
    public static final String ITERATOR_EMPTY = "There are no more elements in this enumeration.";
    public static final String LEASE_CONDITION_ON_SOURCE = "A lease condition cannot be specified on the source of a copy.";
    public static final String LOG_STREAM_END_ERROR = "Error parsing log record: unexpected end of stream.";
    public static final String LOG_STREAM_DELIMITER_ERROR = "Error parsing log record: unexpected delimiter encountered.";
    public static final String LOG_STREAM_QUOTE_ERROR = "Error parsing log record: unexpected quote character encountered.";
    public static final String LOG_VERSION_UNSUPPORTED = "A storage log version of %s is unsupported.";
    public static final String MARK_EXPIRED = "Stream mark expired.";
    public static final String MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION = "The client could not finish the operation within specified maximum execution timeout.";
    public static final String METADATA_KEY_INVALID = "The key for one of the metadata key-value pairs is null, empty, or whitespace.";
    public static final String METADATA_VALUE_INVALID = "The value for one of the metadata key-value pairs is null, empty, or whitespace.";
    public static final String MISSING_CREDENTIALS = "No credentials provided.";
    public static final String MISSING_MANDATORY_DATE_HEADER = "Canonicalization did not find a non-empty x-ms-date header in the request. Please use a request with a valid x-ms-date header in RFC 123 format.";
    public static final String MISSING_MANDATORY_PARAMETER_FOR_SAS = "Missing mandatory parameters for valid Shared Access Signature.";
    public static final String MISSING_MD5 = "ContentMD5 header is missing in the response.";
    public static final String MISSING_NULLARY_CONSTRUCTOR = "Class type must contain contain a nullary constructor.";
    public static final String MULTIPLE_CREDENTIALS_PROVIDED = "Cannot provide credentials as part of the address and as constructor parameter. Either pass in the address or use a different constructor.";
    public static final String NETWORK_ON_MAIN_THREAD_EXCEPTION = "Network operations may not be performed on the main thread.";
    public static final String OPS_IN_BATCH_MUST_HAVE_SAME_PARTITION_KEY = "All entities in a given batch must have the same partition key.";
    public static final String PARAMETER_NOT_IN_RANGE = "The value of the parameter '%s' should be between %s and %s.";
    public static final String PARAMETER_SHOULD_BE_GREATER = "The value of the parameter '%s' should be greater than %s.";
    public static final String PARAMETER_SHOULD_BE_GREATER_OR_EQUAL = "The value of the parameter '%s' should be greater than or equal to %s.";
    public static final String PARTITIONKEY_MISSING_FOR_DELETE = "Delete requires a partition key.";
    public static final String PARTITIONKEY_MISSING_FOR_MERGE = "Merge requires a partition key.";
    public static final String PARTITIONKEY_MISSING_FOR_UPDATE = "Replace requires a partition key.";
    public static final String PARTITIONKEY_MISSING_FOR_INSERT = "Insert requires a partition key.";
    public static final String PATH_STYLE_URI_MISSING_ACCOUNT_INFORMATION = "Missing account name information inside path style URI. Path style URIs should be of the form http://<IPAddress:Port>/<accountName>";
    public static final String PRIMARY_ONLY_COMMAND = "This operation can only be executed against the primary storage location.";
    public static final String PROPERTY_CANNOT_BE_SERIALIZED_AS_GIVEN_EDMTYPE = "Property %s with Edm Type %s cannot be de-serialized.";
    public static final String PRECONDITION_FAILURE_IGNORED = "Pre-condition failure on a retry is being ignored since the request should have succeeded in the first attempt.";
    public static final String QUERY_PARAMETER_NULL_OR_EMPTY = "Cannot encode a query parameter with a null or empty key.";
    public static final String QUERY_REQUIRES_VALID_CLASSTYPE_OR_RESOLVER = "Query requires a valid class type or resolver.";
    public static final String QUEUE = "queue";
    public static final String QUEUE_ENDPOINT_NOT_CONFIGURED = "No queue endpoint configured.";
    public static final String RELATIVE_ADDRESS_NOT_PERMITTED = "Address %s is a relative address. Only absolute addresses are permitted.";
    public static final String RESOURCE_NAME_EMPTY = "Invalid %s name. The name may not be null, empty, or whitespace only.";
    public static final String RESPONSE_RECEIVED_IS_INVALID = "The response received is invalid or improperly formatted.";
    public static final String RETRIEVE_MUST_BE_ONLY_OPERATION_IN_BATCH = "A batch transaction with a retrieve operation cannot contain any other operations.";
    public static final String ROWKEY_MISSING_FOR_DELETE = "Delete requires a row key.";
    public static final String ROWKEY_MISSING_FOR_MERGE = "Merge requires a row key.";
    public static final String ROWKEY_MISSING_FOR_UPDATE = "Replace requires a row key.";
    public static final String ROWKEY_MISSING_FOR_INSERT = "Insert requires a row key.";
    public static final String SCHEME_NULL_OR_EMPTY = "The protocol to use is null. Please specify whether to use http or https.";
    public static final String SECONDARY_ONLY_COMMAND = "This operation can only be executed against the secondary storage location.";
    public static final String SHARE = "share";
    public static final String SNAPSHOT_LISTING_ERROR = "Listing snapshots is only supported in flat mode (no delimiter). Consider setting useFlatBlobListing to true.";
    public static final String SNAPSHOT_QUERY_OPTION_ALREADY_DEFINED = "Snapshot query parameter is already defined in the blob URI. Either pass in a snapshotTime parameter or use a full URL with a snapshot query parameter.";
    public static final String STORAGE_CREDENTIALS_NULL_OR_ANONYMOUS = "StorageCredentials cannot be null or anonymous for this service.";
    public static final String STORAGE_CLIENT_OR_SAS_REQUIRED = "Either a SAS token or a service client must be specified.";
    public static final String STORAGE_URI_MISSING_LOCATION = "The URI for the target storage location is not specified. Please consider changing the request's location mode.";
    public static final String STORAGE_URI_MUST_MATCH = "Primary and secondary location URIs in a StorageUri must point to the same resource.";
    public static final String STORAGE_URI_NOT_NULL = "Primary and secondary location URIs in a StorageUri must not both be null.";
    public static final String STOREAS_DIFFERENT_FOR_GETTER_AND_SETTER = "StoreAs Annotation found for both getter and setter for property %s with unequal values.";
    public static final String STOREAS_USED_ON_EMPTY_PROPERTY = "StoreAs Annotation found for property %s with empty value.";
    public static final String STREAM_CLOSED = "Stream is already closed.";
    public static final String STREAM_LENGTH_GREATER_THAN_4MB = "Invalid stream length, length must be less than or equal to 4 MB in size.";
    public static final String STREAM_LENGTH_NEGATIVE = "Invalid stream length, specify -1 for unknown length stream, or a positive number of bytes.";
    public static final String STRING_NOT_VALID = "The String is not a valid Base64-encoded string.";
    public static final String TABLE = "table";
    public static final String TABLE_ENDPOINT_NOT_CONFIGURED = "No table endpoint configured.";
    public static final String TABLE_OBJECT_RELATIVE_URIS_NOT_SUPPORTED = "Table Object relative URIs not supported.";
    public static final String TAKE_COUNT_ZERO_OR_NEGATIVE = "Take count must be positive and greater than 0.";
    public static final String TOO_MANY_PATH_SEGMENTS = "The count of URL path segments (strings between '/' characters) as part of the blob name cannot exceed 254.";
    public static final String TOO_MANY_SHARED_ACCESS_POLICY_IDENTIFIERS = "Too many %d shared access policy identifiers provided. Server does not support setting more than %d on a single container, queue, or table.";
    public static final String TOO_MANY_SHARED_ACCESS_POLICY_IDS = "Too many %d shared access policy identifiers provided. Server does not support setting more than %d on a single container.";
    public static final String TYPE_NOT_SUPPORTED = "Type %s is not supported.";
    public static final String UNEXPECTED_CONTINUATION_TYPE = "The continuation type passed in is unexpected. Please verify that the correct continuation type is passed in. Expected {%s}, found {%s}.";
    public static final String UNEXPECTED_FIELD_NAME = "Unexpected field name. Expected: '%s'. Actual: '%s'.";
    public static final String UNEXPECTED_STATUS_CODE_RECEIVED = "Unexpected http status code received.";
    public static final String UNEXPECTED_STREAM_READ_ERROR = "Unexpected error. Stream returned unexpected number of bytes.";
    public static final String UNKNOWN_TABLE_OPERATION = "Unknown table operation.";
}
import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.TimeZone;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.IPRange;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.ResponseReceivedEvent;
import com.microsoft.azure.storage.SecondaryTests;
import com.microsoft.azure.storage.SendingRequestEvent;
import com.microsoft.azure.storage.SharedAccessAccountPermissions;
import com.microsoft.azure.storage.SharedAccessAccountPolicy;
import com.microsoft.azure.storage.SharedAccessAccountResourceType;
import com.microsoft.azure.storage.SharedAccessAccountService;
import com.microsoft.azure.storage.SharedAccessProtocols;
import com.microsoft.azure.storage.StorageCredentials;
import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature;
import com.microsoft.azure.storage.StorageEvent;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.StorageUri;
import com.microsoft.azure.storage.TestHelper;
import com.microsoft.azure.storage.TestRunners;
import com.microsoft.azure.storage.TestRunners.CloudTests;
import com.microsoft.azure.storage.TestRunners.DevFabricTests;
import com.microsoft.azure.storage.TestRunners.DevStoreTests;
import com.microsoft.azure.storage.TestRunners.SlowTests;
import com.microsoft.azure.storage.blob.BlobProperties;
import com.microsoft.azure.storage.blob.BlobTestHelper;
import com.microsoft.azure.storage.blob.CloudBlobClient;
import com.microsoft.azure.storage.blob.CloudBlobContainer;
import com.microsoft.azure.storage.blob.CloudBlockBlob;
import com.microsoft.azure.storage.blob.SharedAccessBlobPermissions;
import com.microsoft.azure.storage.blob.SharedAccessBlobPolicy;
import com.microsoft.azure.storage.core.PathUtility;
import com.microsoft.azure.storage.core.SR;
import junit.framework.Assert;
/**
 * 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.file;






@Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class FileSasTests {

    protected static CloudFileClient fileClient = null;
    protected CloudFileShare share;
    protected CloudFile file;

    @Before
    public void fileSASTestMethodSetUp() throws URISyntaxException, StorageException, IOException {
        if (fileClient == null) {
            fileClient = TestHelper.createCloudFileClient();
        }
        this.share = FileTestHelper.getRandomShareReference();
        this.share.create();

        this.file = (CloudFile) FileTestHelper.uploadNewFile(this.share, 100, null);
    }

    @After
    public void fileSASTestMethodTearDown() throws StorageException {
        this.share.deleteIfExists();
    }

    @Test
    public void testApiVersion() throws InvalidKeyException, StorageException, URISyntaxException {
        SharedAccessFilePolicy policy = createSharedAccessPolicy(
                EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE,
                        SharedAccessFilePermissions.LIST, SharedAccessFilePermissions.DELETE), 300);
        String sas = this.file.generateSharedAccessSignature(policy, null);
        
        // should not be appended before signing
        assertEquals(-1, sas.indexOf(Constants.QueryConstants.API_VERSION));