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;
    }
} public final class StorageErrorCodeStrings {
    /**
     * The specified account already exists.
     */
    public static final String ACCOUNT_ALREADY_EXISTS = "AccountAlreadyExists";

    /**
     * The specified account is in the process of being created.
     */
    public static final String ACCOUNT_BEING_CREATED = "AccountBeingCreated";

    /**
     * The specified account is disabled.
     */
    public static final String ACCOUNT_IS_DISABLED = "AccountIsDisabled";

    /**
     * Authentication failed.
     */
    public static final String AUTHENTICATION_FAILED = "AuthenticationFailed";

    /**
     * The specified blob already exists.
     */
    public static final String BLOB_ALREADY_EXISTS = "BlobAlreadyExists";

    /**
     * The specified blob does not exist.
     */
    public static final String BLOB_NOT_FOUND = "BlobNotFound";

    /**
     * Could not verify the copy source within the specified time. Examine the HTTP status code and message for more
     * information about the failure.
     */
    public static final String CANNOT_VERIFY_COPY_SOURCE = "CannotVerifyCopySource";

    /**
     * The file or directory could not be deleted because it is in use by an SMB client.
     */
    public static final String CANNOT_DELETE_FILE_OR_DIRECTORY = "CannotDeleteFileOrDirectory";

    /**
     * The specified resource state could not be flushed from an SMB client in the specified time.
     */
    public static final String CLIENT_CACHE_FLUSH_DELAY = "ClientCacheFlushDelay";

    /**
     * Condition headers are not supported.
     */
    public static final String CONDITION_HEADERS_NOT_SUPPORTED = "ConditionHeadersNotSupported";

    /**
     * The specified condition was not met.
     */
    public static final String CONDITION_NOT_MET = "ConditionNotMet";

    /**
     * The specified container already exists.
     */
    public static final String CONTAINER_ALREADY_EXISTS = "ContainerAlreadyExists";

    /**
     * The specified container is being deleted.
     */
    public static final String CONTAINER_BEING_DELETED = "ContainerBeingDeleted";

    /**
     * The specified container is disabled.
     */
    public static final String CONTAINER_DISABLED = "ContainerDisabled";

    /**
     * The specified container was not found.
     */
    public static final String CONTAINER_NOT_FOUND = "ContainerNotFound";

    /**
     * The copy source account and destination account must be the same.
     */
    public static final String COPY_ACROSS_ACCOUNTS_NOT_SUPPORTED = "CopyAcrossAccountsNotSupported";

    /**
     * The specified copy ID did not match the copy ID for the pending copy operation.
     */
    public static final String COPY_ID_MISMATCH = "CopyIdMismatch";

    /**
     * The specified resource is marked for deletion by an SMB client.
     */
    public static final String DELETE_PENDING = "DeletePending";

    /**
     * The specified directory already exists.
     */
    public static final String DIRECTORY_ALREADY_EXISTS = "DirectoryAlreadyExists";

    /**
     * The specified directory is not empty.
     */
    public static final String DIRECTORY_NOT_EMPTY = "DirectoryNotEmpty";

    /**
     * A property is specified more than one time.
     */
    public static final String DUPLICATE_PROPERTIES_SPECIFIED = "DuplicatePropertiesSpecified";
    /**
     * The metadata key is empty.
     */
    public static final String EMPTY_METADATA_KEY = "EmptyMetadataKey";

    /**
     * The entity already exists
     */
    public static final String ENTITY_ALREADY_EXISTS = "EntityAlreadyExists";

    /**
     * The entity already exists
     */
    public static final String ENTITY_TOO_LARGE = "EntityTooLarge";

    /**
     * A portion of the specified file is locked by an SMB client.
     */
    public static final String FILE_LOCK_CONFLICT = "FileLockConflict";

    /**
     * The required host information is not present in the request. You must send a non-empty Host header or include the
     * absolute URI in the request line.
     */
    public static final String HOST_INFORMATION_NOT_PRESENT = "HostInformationNotPresent";

    /**
     * An incorrect blob type was specified.
     */
    public static final String INCORRECT_BLOB_TYPE = "IncorrectBlobType";

    /**
     * The lease ID matched, but the specified lease must be an infinite-duration lease.
     */
    public static final String INFINITE_LEASE_DURATION_REQUIRED = "InfiniteLeaseDurationRequired";

    /**
     * The account being accessed does not have sufficient permissions to execute this operation.
     */
    public static final String INSUFFICIENT_ACCOUNT_PERMISSIONS = "InsufficientAccountPermissions";

    /**
     * An internal error occurred.
     */
    public static final String INTERNAL_ERROR = "InternalError";

    /**
     * The authentication information was not provided in the correct format. Verify the value of Authorization header.
     */
    public static final String INVALID_AUTHENTICATION_INFO = "InvalidAuthenticationInfo";

    /**
     * Error code that may be returned when the specified append offset is invalid.
     */
    public static final String INVALID_APPEND_POSITION = "AppendPositionConditionNotMet";
    
    /**
     * An incorrect blob type was specified.
     */
    public static final String INVALID_BLOB_TYPE = "InvalidBlobType";

    /**
     * The specified blob or block content is invalid.
     */
    public static final String INVALID_BLOB_OR_BLOCK = "InvalidBlobOrBlock";

    /**
     * The specified block ID is invalid. The block ID must be Base64-encoded.
     */
    public static final String INVALID_BLOCK_ID = "InvalidBlockId";

    /**
     * The specified block list is invalid.
     */
    public static final String INVALID_BLOCK_LIST = "InvalidBlockList";

    /**
     * One or more header values are invalid.
     */
    public static final String INVALID_HEADER_VALUE = "InvalidHeaderValue";

    /**
     * The HTTP verb is invalid.
     */
    public static final String INVALID_HTTP_VERB = "InvalidHttpVerb";

    /**
     * The input is invalid.
     */
    public static final String INVALID_INPUT = "InvalidInput";

    /**
     * The specified marker is invalid.
     */
    public static final String INVALID_MARKER = "InvalidMarker";

    /**
     * Error code that may be returned when the specified max blob size is exceeded.
     */
    public static final String INVALID_MAX_BLOB_SIZE_CONDITION = "MaxBlobSizeConditionNotMet"; 

    /**
     * The specified MD5 hash is invalid.
     */
    public static final String INVALID_MD5 = "InvalidMd5";

    /**
     * The specified metadata is invalid.
     */
    public static final String INVALID_METADATA = "InvalidMetadata";

    /**
     * The page range specified is invalid.
     */
    public static final String INVALID_PAGE_RANGE = "InvalidPageRange";

    /**
     * One or more query parameters are invalid.
     */
    public static final String INVALID_QUERY_PARAMETER_VALUE = "InvalidQueryParameterValue";

    /**
     * The specified range is invalid.
     */
    public static final String INVALID_RANGE = "InvalidRange";

    /**
     * The specified resource name contains invalid characters.
     */
    public static final String INVALID_RESOURCE_NAME = "InvalidResourceName";

    /**
     * The URI is invalid.
     */
    public static final String INVALID_URI = "InvalidUri";

    /**
     * The value specified is invalid.
     */
    public static final String INVALID_VALUE_TYPE = "InvalidValueType";

    /**
     * All operations on page blobs require at least version 2009-09-19.
     */
    public static final String INVALID_VERSION_FOR_PAGE_BLOB_OPERATION = "InvalidVersionForPageBlobOperation";

    /**
     * The specified XML document is invalid.
     */
    public static final String INVALID_XML_DOCUMENT = "InvalidXmlDocument";

    /**
     * The value provided for one of the XML nodes in the request body was not in the correct format.
     */
    public static final String INVALID_XML_NODE_VALUE = "InvalidXmlNodeValue";

    /**
     * The specified XML or Json document is invalid. Used for tables only.
     */
    public static final String INVALID_DOCUMENT = "InvalidDocument";

    /**
     * File or directory path is too long or file or directory path has too many subdirectories.
     */
    public static final String INVALID_FILE_OR_DIRECTORY_PATH_NAME = "InvalidFileOrDirectoryPathName";

    /**
     * The incorrect type was given. Used for tables only.
     */
    public static final String INVALID_TYPE = "InvalidType";

    /**
     * JSON format is not supported.
     */
    public static final String JSON_FORMAT_NOT_SUPPORTED = "JsonFormatNotSupported";

    /**
     * The lease is already broken.
     */
    public static final String LEASE_ALREADY_BROKEN = "LeaseAlreadyBroken";

    /**
     * The lease is already present.
     */
    public static final String LEASE_ALREADY_PRESENT = "LeaseAlreadyPresent";

    /**
     * The lease ID is incorrect with a blob operation.
     */
    public static final String LEASE_ID_MISMATCH_WITH_BLOB_OPERATION = "LeaseIdMismatchWithBlobOperation";

    /**
     * The lease ID is incorrect with a container operation.
     */
    public static final String LEASE_ID_MISMATCH_WITH_CONTAINER_OPERATION = "LeaseIdMismatchWithContainerOperation";

    /**
     * The lease ID is incorrect with a lease operation.
     */
    public static final String LEASE_ID_MISMATCH_WITH_LEASE_OPERATION = "LeaseIdMismatchWithLeaseOperation";

    /**
     * The lease ID is missing.
     */
    public static final String LEASE_ID_MISSING = "LeaseIdMissing";

    /**
     * The lease ID matched, but the lease has been broken explicitly and cannot be renewed.
     */
    public static final String LEASE_IS_BROKEN_AND_CANNOT_BE_RENEWED = "LeaseIsBrokenAndCannotBeRenewed";

    /**
     * The lease ID matched, but the lease is currently in breaking state and cannot be acquired until it is broken.
     */
    public static final String LEASE_IS_BREAKING_AND_CANNOT_BE_ACQUIRED = "LeaseIsBreakingAndCannotBeAcquired";

    /**
     * The lease ID matched, but the lease is currently in breaking state and cannot be changed.
     */
    public static final String LEASE_IS_BREAKING_AND_CANNOT_BE_CHANGED = "LeaseIsBreakingAndCannotBeChanged";

    /**
     * A lease ID was specified, but the lease for the blob/container has expired.
     */
    public static final String LEASE_LOST = "LeaseLost";

    /**
     * There is currently no lease on the blob.
     */
    public static final String LEASE_NOT_PRESENT_WITH_BLOB_OPERATION = "LeaseNotPresentWithBlobOperation";

    /**
     * There is currently no lease on the container.
     */
    public static final String LEASE_NOT_PRESENT_WITH_CONTAINER_OPERATION = "LeaseNotPresentWithContainerOperation";

    /**
     * There is currently no lease on the blob/container.
     */
    public static final String LEASE_NOT_PRESENT_WITH_LEASE_OPERATION = "LeaseNotPresentWithLeaseOperation";

    /**
     * The specified MD5 hash does not match the server value.
     */
    public static final String MD5_MISMATCH = "Md5Mismatch";

    /**
     * The message exceeds the maximum allowed size.
     */
    public static final String MESSAGE_TOO_LARGE = "MessageTooLarge";

    /**
     * The specified message does not exist.
     */
    public static final String MESSAGE_NOT_FOUND = "MessageNotFound";

    /**
     * The specified metadata is too large.
     */
    public static final String METADATA_TOO_LARGE = "MetadataTooLarge";

    /**
     * The requested method is not allowed on the specified resource.
     */
    public static final String METHOD_NOT_ALLOWED = "MethodNotAllowed";

    /**
     * The Content-Length header is required for this request.
     */
    public static final String MISSING_CONTENT_LENGTH_HEADER = "MissingContentLengthHeader";

    /**
     * A required header was missing.
     */
    public static final String MISSING_REQUIRED_HEADER = "MissingRequiredHeader";

    /**
     * A required query parameter is missing.
     */
    public static final String MISSING_REQUIRED_QUERY_PARAMETER = "MissingRequiredQueryParameter";

    /**
     * A required XML node was missing.
     */
    public static final String MISSING_REQUIRED_XML_NODE = "MissingRequiredXmlNode";

    /**
     * The MD5 hash is missing.
     */
    public static final String MISSING_MD5_HEADER = "MissingContentMD5Header";

    /**
     * Multiple condition headers are not supported.
     */
    public static final String MULTIPLE_CONDITION_HEADERS_NOT_SUPPORTED = "MultipleConditionHeadersNotSupported";

    /**
     * There is currently no pending copy operation.
     */
    public static final String NO_PENDING_COPY_OPERATION = "NoPendingCopyOperation";

    /**
     * The requested operation is not implemented on the specified resource.
     */
    public static final String NOT_IMPLEMENTED = "NotImplemented";

    /**
     * The operation timed out.
     */
    public static final String OPERATION_TIMED_OUT = "OperationTimedOut";

    /**
     * The input is out of range.
     */
    public static final String OUT_OF_RANGE_INPUT = "OutOfRangeInput";

    /**
     * One or more query parameters are out of range.
     */
    public static final String OUT_OF_RANGE_QUERY_PARAMETER_VALUE = "OutOfRangeQueryParameterValue";

    /**
     * The specified parent path does not exist.
     */
    public static final String PARENT_NOT_FOUND = "ParentNotFound";

    /**
     * There is currently a pending copy operation.
     */
    public static final String PENDING_COPY_OPERATION = "PendingCopyOperation";

    /**
     * The specified pop receipt did not match the pop receipt for a dequeued message.
     */
    public static final String POP_RECEIPT_MISMATCH = "PopReceiptMismatch";

    /**
     * Values have not been specified for all properties in the entity.
     */
    public static final String PROPERTIES_NEED_VALUE = "PropertiesNeedValue";

    /**
     * The property name is invalid.
     */
    public static final String PROPERTY_NAME_INVALID = "PropertyNameInvalid";

    /**
     * The property name exceeds the maximum allowed length.
     */
    public static final String PROPERTY_NAME_TOO_LONG = "PropertyNameTooLong";

    /**
     * The property value is larger than the maximum size permitted.
     */
    public static final String PROPERTY_VALUE_TOO_LARGE = "PropertyValueTooLarge";

    /**
     * The specified queue already exists.
     */
    public static final String QUEUE_ALREADY_EXISTS = "QueueAlreadyExists";

    /**
     * The specified queue is being deleted.
     */
    public static final String QUEUE_BEING_DELETED = "QueueBeingDeleted";

    /**
     * The specified queue has been disabled by the administrator.
     */
    public static final String QUEUE_DISABLED = "QueueDisabled";

    /**
     * The specified queue is not empty.
     */
    public static final String QUEUE_NOT_EMPTY = "QueueNotEmpty";

    /**
     * The specified queue does not exist.
     */
    public static final String QUEUE_NOT_FOUND = "QueueNotFound";

    /**
     * The specified resource is read-only and cannot be modified at this time.
     */
    public static final String READ_ONLY_ATTRIBUTE = "ReadOnlyAttribute";

    /**
     * The request body is too large.
     */
    public static final String REQUEST_BODY_TOO_LARGE = "RequestBodyTooLarge";

    /**
     * The url in the request could not be parsed.
     */
    public static final String REQUEST_URL_FAILED_TO_PARSE = "RequestUrlFailedToParse";

    /**
     * The specified resource was not found.
     */
    public static final String RESOURCE_NOT_FOUND = "ResourceNotFound";

    /**
     * The specified resource already exists.
     */
    public static final String RESOURCE_ALREADY_EXISTS = "ResourceAlreadyExists";

    /**
     * The specified resource type does not match the type of the existing resource.
     */
    public static final String RESOURCE_TYPE_MISMATCH = "ResourceTypeMismatch";

    /**
     * The sequence number condition specified was not met.
     */
    public static final String SEQUENCE_NUMBER_CONDITION_NOT_MET = "SequenceNumberConditionNotMet";

    /**
     * The sequence number increment cannot be performed because it would result in overflow of the sequence number.
     */
    public static final String SEQUENCE_NUMBER_INCREMENT_TOO_LARGE = "SequenceNumberIncrementTooLarge";

    /**
     * The server is busy.
     */
    public static final String SERVER_BUSY = "ServerBusy";

    /**
     * The specified share already exists.
     */
    public static final String SHARE_ALREADY_EXISTS = "ShareAlreadyExists";

    /**
     * The specified share is being deleted. Try operation later.
     */
    public static final String SHARE_BEING_DELETED = "ShareBeingDeleted";

    /**
     * The specified share is disabled by the administrator.
     */
    public static final String SHARE_DISABLED = "ShareDisabled";

    /**
     * The specified share contains snapshots.
     */
    public static final String SHARE_HAS_SNAPSHOTS = "ShareHasSnapshots";

    /**
     * The specified share was not found.
     */
    public static final String SHARE_NOT_FOUND = "ShareNotFound";

    /**
     * The specified resource may be in use by an SMB client.
     */
    public static final String SHARING_VIOLATION = "SharingViolation";

    /**
     * This operation is not permitted because the blob has snapshots.
     */
    public static final String SNAPSHOTS_PRESENT = "SnapshotsPresent";

    /**
     * The source condition specified using HTTP conditional header(s) is not met.
     */
    public static final String SOURCE_CONDITION_NOT_MET = "SourceConditionNotMet";

    /**
     * The target condition specified using HTTP conditional header(s) is not met.
     */
    public static final String TARGET_CONDITION_NOT_MET = "TargetConditionNotMet";

    /**
     * The table specified already exists.
     */
    public static final String TABLE_ALREADY_EXISTS = "TableAlreadyExists";

    /**
     * The specified table is being deleted.
     */
    public static final String TABLE_BEING_DELETED = "TableBeingDeleted";

    /**
     * The table specified does not exist.
     */
    public static final String TABLE_NOT_FOUND = "TableNotFound";

    /**
     * The entity contains more properties than allowed.
     */
    public static final String TOO_MANY_PROPERTIES = "TooManyProperties";

    /**
     * The update condition was not satisfied
     */
    public static final String UPDATE_CONDITION_NOT_SATISFIED = "UpdateConditionNotSatisfied";

    /**
     * One or more header values are not supported.
     */
    public static final String UNSUPPORTED_HEADER = "UnsupportedHeader";

    /**
     * One of the XML nodes specified in the request body is not supported.
     */
    public static final String UNSUPPORTED_XML_NODE = "UnsupportedXmlNode";

    /**
     * The specified HTTP verb is not supported.
     */
    public static final String UNSUPPORTED_HTTP_VERB = "UnsupportedHttpVerb";

    /**
     * One or more query parameters is not supported.
     */
    public static final String UNSUPPORTED_QUERY_PARAMETER = "UnsupportedQueryParameter";

    /**
     * More than one X-HTTP-Method is specified.
     */
    public static final String X_METHOD_INCORRECT_COUNT = "XMethodIncorrectCount";

    /**
     * The specified X-HTTP-Method is invalid.
     */
    public static final String X_METHOD_INCORRECT_VALUE = "XMethodIncorrectValue";

    /**
     * The request uses X-HTTP-Method with an HTTP verb other than POST.
     */
    public static final String X_METHOD_NOT_USING_POST = "XMethodNotUsingPost";

    /**
     * Private Default Constructor.
     */
    private StorageErrorCodeStrings() {
        // No op
    }
} public interface CloudTests {
} public interface DevFabricTests {
} public interface DevStoreTests {
} public interface SlowTests {
} public abstract class CloudBlob implements ListBlobItem {
    /**
     * Holds the metadata for the blob.
     */
    HashMap<String, String> metadata = new HashMap<String, String>();

    /**
     * Holds the properties of the blob.
     */
    BlobProperties properties;

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

    /**
     * Holds the snapshot ID.
     */
    String snapshotID;

    /**
     * Holds the blob's container reference.
     */
    private CloudBlobContainer container;

    /**
     * Represents the blob's directory.
     */
    protected CloudBlobDirectory parent;

    /**
     * Holds the blob's name.
     */
    private String name;

    /**
     * Holds the number of bytes to buffer when writing to a {@link BlobOutputStream} (block and page blobs).
     */
    protected int streamWriteSizeInBytes = Constants.DEFAULT_STREAM_WRITE_IN_BYTES;

    /**
     * Holds the minimum read size when using a {@link BlobInputStream}.
     */
    protected int streamMinimumReadSizeInBytes = Constants.DEFAULT_MINIMUM_READ_SIZE_IN_BYTES;

    /**
     * Represents the blob client.
     */
    protected CloudBlobClient blobServiceClient;
    
    /**
     * Creates an instance of the <code>CloudBlob</code> class using the specified type, name, snapshot ID, and
     * container.
     *
     * @param type
     *            A {@link BlobType} value which represents the type of the blob.
     * @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 CloudBlob(final BlobType type, String blobName, String snapshotID, CloudBlobContainer container)
            throws URISyntaxException {
        Utility.assertNotNullOrEmpty("blobName", blobName);
        Utility.assertNotNull("container", container);

        this.storageUri = PathUtility.appendPathToUri(container.getStorageUri(), blobName);
        this.name = blobName;
        this.blobServiceClient = container.getServiceClient();
        this.container = container;
        this.snapshotID = snapshotID;
        this.properties = new BlobProperties(type);
    }
    
    /**
     * Creates an instance of the <code>CloudBlob</code> class using the specified URI, snapshot ID, and cloud blob
     * client.
     *
     * @param type
     *            A {@link BlobType} value which represents the type of the blob.
     * @param uri
     *            A {@link StorageUri} object that represents the URI to the blob, beginning with the container name.
     * @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.
     */
    protected CloudBlob(final BlobType type, final StorageUri uri, final String snapshotID,
            final StorageCredentials credentials) throws StorageException {
        this.properties = new BlobProperties(type);
        this.parseQueryAndVerify(uri, credentials);

        if (snapshotID != null) {
            if (this.snapshotID != null) {
                throw new IllegalArgumentException(SR.SNAPSHOT_QUERY_OPTION_ALREADY_DEFINED);
            }
            else {
                this.snapshotID = snapshotID;
            }
        }
    }

    /**
     * Creates an instance of the <code>CloudBlob</code> class by copying values from another blob.
     *
     * @param otherBlob
     *            A <code>CloudBlob</code> object that represents the blob to copy.
     */
    protected CloudBlob(final CloudBlob otherBlob) {
        this.properties = new BlobProperties(otherBlob.properties);

        if (otherBlob.metadata != null) {
            this.metadata = new HashMap<String, String>();
            for (final String key : otherBlob.metadata.keySet()) {
                this.metadata.put(key, otherBlob.metadata.get(key));
            }
        }

        this.snapshotID = otherBlob.snapshotID;
        this.storageUri = otherBlob.storageUri;
        this.container = otherBlob.container;
        this.parent = otherBlob.parent;
        this.blobServiceClient = otherBlob.blobServiceClient;
        this.name = otherBlob.name;
        this.setStreamMinimumReadSizeInBytes(otherBlob.getStreamMinimumReadSizeInBytes());
        this.setStreamWriteSizeInBytes(otherBlob.getStreamWriteSizeInBytes());
    }

    /**
     * Aborts an ongoing blob copy operation.
     *
     * @param copyId
     *            A <code>String</code> object that identifying the copy operation.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final void abortCopy(final String copyId) throws StorageException {
        this.abortCopy(copyId, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Aborts an ongoing blob copy operation.
     *
     * @param copyId
     *            A <code>String</code> object that identifying the copy operation.
     *
     * @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 final void abortCopy(final String copyId, final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest<CloudBlobClient, CloudBlob, Void> abortCopyImpl(final String copyId,
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        Utility.assertNotNull("copyId", copyId);

        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 {
                return BlobRequest.abortCopy(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
                        options, context, accessCondition, copyId);
            }

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

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

                return null;
            }
        };

        return putRequest;
    }

    /**
     * Acquires a new infinite lease on the blob.
     *
     * @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 blob 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 blob 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 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 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, this.properties.getBlobType(), this.blobServiceClient);

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

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

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
                    throws Exception {
                return BlobRequest.leaseBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
                        options, context, accessCondition, LeaseAction.ACQUIRE, leaseTimeInSeconds, proposedLeaseId,
                        null);
            }

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

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

                updateEtagAndLastModifiedFromResponse(this.getConnection());
                blob.properties.setLeaseStatus(LeaseStatus.LOCKED);

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

        return putRequest;
    }

    /**
     * Asserts that the blob has the correct blob type specified in the blob attributes.
     *
     * @throws StorageException
     *             If an incorrect blob type is used.
     */
    protected final void assertCorrectBlobType() throws StorageException {
        if (this instanceof CloudBlockBlob && this.properties.getBlobType() != BlobType.BLOCK_BLOB) {
            throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE, String.format(SR.INVALID_BLOB_TYPE,
                    BlobType.BLOCK_BLOB, this.properties.getBlobType()), Constants.HeaderConstants.HTTP_UNUSED_306,
                    null, null);
        } 
        else if (this instanceof CloudPageBlob && this.properties.getBlobType() != BlobType.PAGE_BLOB) {
            throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE, String.format(SR.INVALID_BLOB_TYPE,
                    BlobType.PAGE_BLOB, this.properties.getBlobType()), Constants.HeaderConstants.HTTP_UNUSED_306,
                    null, null);
        } 
        else if (this instanceof CloudAppendBlob && this.properties.getBlobType() != BlobType.APPEND_BLOB) {
            throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE, String.format(SR.INVALID_BLOB_TYPE,
                    BlobType.APPEND_BLOB, this.properties.getBlobType()), Constants.HeaderConstants.HTTP_UNUSED_306,
                    null, null);
        }
    }

    /**
     * Asserts that write operation is not done for snapshot.
     */
    protected void assertNoWriteOperationForSnapshot() {
        if (isSnapshot()) {
            throw new IllegalArgumentException(SR.INVALID_OPERATION_FOR_A_SNAPSHOT);
        }
    }

    /**
     * Breaks the lease and ensures that another client cannot acquire a new lease until the current lease period
     * has expired.
     *
     * @param breakPeriodInSeconds
     *            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 An <code>long</code> which specifies 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 /* options */, null /* opContext */);
    }

    /**
     * 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 An <code>long</code> which represents 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, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest<CloudBlobClient, CloudBlob, Long> breakLeaseImpl(final Integer breakPeriodInSeconds,
            final AccessCondition accessCondition, final BlobRequestOptions options) {

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

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
                    throws Exception {
                return BlobRequest.leaseBlob(blob.getTransformedAddress(context).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(CloudBlob blob, CloudBlobClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) {
                    this.setNonExceptionedRetryableFailure(true);
                    return -1L;
                }

                updateEtagAndLastModifiedFromResponse(this.getConnection());

                final String leaseTime = BlobResponse.getLeaseTime(this.getConnection());

                blob.properties.setLeaseStatus(LeaseStatus.UNLOCKED);
                return Utility.isNullOrEmpty(leaseTime) ? -1L : Long.parseLong(leaseTime);
            }
        };

        return putRequest;
    }

    /**
     * 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 /* options */, null /* opContext */);
    }

    /**
     * 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,
     *            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.
     * @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("accessCondition", accessCondition);
        Utility.assertNotNullOrEmpty("leaseID", accessCondition.getLeaseID());

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

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

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
                    throws Exception {
                return BlobRequest.leaseBlob(blob.getTransformedAddress(context).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(CloudBlob blob, CloudBlobClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                updateEtagAndLastModifiedFromResponse(this.getConnection());

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

        return putRequest;
    }

    /**
     * Requests the service to start copying a URI's contents, properties, and metadata to a new blob.
     *
     * @param source
     *            A <code>java.net.URI</code> The source URI. URIs for resources outside of Azure
     *            may only be copied into block blobs.
     *
     * @return A <code>String</code> which represents the copy ID associated with the copy operation.
     *
     * @throws StorageException
     *            If a storage service error occurred.
     */
    @DoesServiceRequest
    public final String startCopy(final URI source) throws StorageException {
        return this.startCopy(source, null /* sourceAccessCondition */, null /* destinationAccessCondition */,
                null /* options */, null /* opContext */);
    }

    /**
     * Requests the service to start copying a URI's contents, properties, and metadata to a new blob, using the
     * specified access conditions, lease ID, request options, and operation context.
     *
     * @param source
     *            A <code>java.net.URI</code> The source URI.  URIs for resources outside of Azure
     *            may only be copied into block blobs.
     * @param sourceAccessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the source.
     * @param destinationAccessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the destination.
     * @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.
     *
     */
    @DoesServiceRequest
    public final String startCopy(final URI source, final AccessCondition sourceAccessCondition,
                                  final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        return this.startCopy(source, null /* premiumPageBlobTier */, sourceAccessCondition, destinationAccessCondition, options, opContext);
    }

    /**
     * Requests the service to start copying a URI's contents, properties, and metadata to a new blob, using the
     * specified premium page blob tier, access conditions, lease ID, request options, and operation context.
     * <p>
     * Note: Setting the premiumPageBlobTier is only supported for premium accounts.
     * </p>
     * @param source
     *            A <code>java.net.URI</code> The source URI.  URIs for resources outside of Azure
     *            may only be copied into block blobs.
     * @param premiumPageBlobTier
     *            A {@link PremiumPageBlobTier} object which represents the tier of the blob.
     * @param sourceAccessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the source.
     * @param destinationAccessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the destination.
     * @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.
     *
     */
    @DoesServiceRequest
    protected final String startCopy(final URI source, final PremiumPageBlobTier premiumPageBlobTier, final AccessCondition sourceAccessCondition,
            final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.startCopyImpl(source, false /* incrementalCopy */, premiumPageBlobTier, sourceAccessCondition, destinationAccessCondition, options),
                options.getRetryPolicyFactory(), opContext);
    }

    protected StorageRequest<CloudBlobClient, CloudBlob, String> startCopyImpl(
            final URI source, final boolean incrementalCopy, final PremiumPageBlobTier premiumPageBlobTier, final AccessCondition sourceAccessCondition,
            final AccessCondition destinationAccessCondition, final BlobRequestOptions options) {

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

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
                    throws Exception {
                // toASCIIString() must be used in order to appropriately encode the URI
                return BlobRequest.copyFrom(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
                        options, context, sourceAccessCondition, destinationAccessCondition, source.toASCIIString(),
                        blob.snapshotID, incrementalCopy, premiumPageBlobTier);
            }

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

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

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

                blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
                blob.properties.setCopyState(BlobResponse.getCopyState(this.getConnection()));
                blob.properties.setPremiumPageBlobTier(premiumPageBlobTier);
                if (premiumPageBlobTier != null) {
                    blob.properties.setBlobTierInferred(false);
                }

                return blob.properties.getCopyState().getCopyId();
            }
        };

        return putRequest;
    }

    /**
     * Creates a snapshot of the blob.
     *
     * @return A <code>CloudBlob</code> object that represents the snapshot of the blob.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final CloudBlob createSnapshot() throws StorageException {
        return this
                .createSnapshot(null /* metadata */, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Creates a snapshot of the blob using the specified request options and operation context.
     *
     * @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>CloudBlob</code> object that represents the snapshot of the blob.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final CloudBlob createSnapshot(final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        return this.createSnapshot(null /* metadata */, accessCondition, options, opContext);
    }

    /**
     * Creates a snapshot of the blob using the specified request options and operation context.
     *
     * @param metadata
     *            A collection of name-value pairs defining the metadata of the snapshot, or null.
     * @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>CloudBlob</code> object that represents the snapshot of the blob.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final CloudBlob createSnapshot(final HashMap<String, String> metadata,
            final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        assertNoWriteOperationForSnapshot();

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest<CloudBlobClient, CloudBlob, CloudBlob> createSnapshotImpl(
            final HashMap<String, String> metadata, final AccessCondition accessCondition,
            final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlob, CloudBlob> putRequest = new StorageRequest<CloudBlobClient, CloudBlob, CloudBlob>(
                options, this.getStorageUri()) {

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

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

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

            @Override
            public CloudBlob preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }
                CloudBlob snapshot = null;
                final String snapshotTime = BlobResponse.getSnapshotTime(this.getConnection());
                if (blob instanceof CloudBlockBlob) {
                    snapshot = new CloudBlockBlob(blob.getName(), snapshotTime, CloudBlob.this.getContainer());
                }
                else if (blob instanceof CloudPageBlob) {
                    snapshot = new CloudPageBlob(blob.getName(), snapshotTime, CloudBlob.this.getContainer());
                }
                else if (blob instanceof CloudAppendBlob) {
                    snapshot = new CloudAppendBlob(blob.getName(), snapshotTime, CloudBlob.this.getContainer());
                }
                snapshot.setProperties(blob.properties);

                // use the specified metadata if not null : otherwise blob's metadata
                snapshot.setMetadata(metadata != null ? metadata : blob.metadata);

                snapshot.updateEtagAndLastModifiedFromResponse(this.getConnection());

                return snapshot;
            }
        };

        return putRequest;
    }

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

    /**
     * Deletes the blob using the specified snapshot and request options, and operation context.
     * <p>
     * A blob that has snapshots cannot be deleted unless the snapshots are also deleted. If a blob has snapshots, use
     * the {@link DeleteSnapshotsOption#DELETE_SNAPSHOTS_ONLY} or {@link DeleteSnapshotsOption#INCLUDE_SNAPSHOTS} value
     * in the <code>deleteSnapshotsOption</code> parameter to specify how the snapshots should be handled when the blob
     * is deleted.
     *
     * @param deleteSnapshotsOption
     *            A {@link DeleteSnapshotsOption} object that indicates whether to delete only snapshots, or the blob
     *            and its snapshots.
     * @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 final void delete(final DeleteSnapshotsOption deleteSnapshotsOption, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException {
        Utility.assertNotNull("deleteSnapshotsOption", deleteSnapshotsOption);

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

    /**
     * Deletes the blob if it exists.
     * <p>
     * A blob that has snapshots cannot be deleted unless the snapshots are also deleted. If a blob has snapshots, use
     * the {@link DeleteSnapshotsOption#DELETE_SNAPSHOTS_ONLY} or {@link DeleteSnapshotsOption#INCLUDE_SNAPSHOTS} value
     * in the <code>deleteSnapshotsOption</code> parameter to specify how the snapshots should be handled when the blob
     * is deleted.
     *
     * @return <code>true</code> if the blob was deleted; otherwise, <code>false</code>.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     *
     */
    @DoesServiceRequest
    public final boolean deleteIfExists() throws StorageException {
        return this
                .deleteIfExists(DeleteSnapshotsOption.NONE, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Deletes the blob if it exists, using the specified snapshot and request options, and operation context.
     * <p>
     * A blob that has snapshots cannot be deleted unless the snapshots are also deleted. If a blob has snapshots, use
     * the {@link DeleteSnapshotsOption#DELETE_SNAPSHOTS_ONLY} or {@link DeleteSnapshotsOption#INCLUDE_SNAPSHOTS} value
     * in the <code>deleteSnapshotsOption</code> parameter to specify how the snapshots should be handled when the blob
     * is deleted.
     *
     * @param deleteSnapshotsOption
     *            A {@link DeleteSnapshotsOption} object that indicates whether to delete only snapshots, or the blob
     *            and its snapshots.
     * @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 <code>true</code> if the blob existed and was deleted; otherwise, <code>false</code>
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final boolean deleteIfExists(final DeleteSnapshotsOption deleteSnapshotsOption,
            final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

        }
        else {
            return false;
        }
    }

    private StorageRequest<CloudBlobClient, CloudBlob, Void> deleteImpl(
            final DeleteSnapshotsOption deleteSnapshotsOption, final AccessCondition accessCondition,
            final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlob, Void> deleteRequest = new StorageRequest<CloudBlobClient, CloudBlob, Void>(
                options, this.getStorageUri()) {

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

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

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

                return null;
            }
        };

        return deleteRequest;
    }

    /**
     * Downloads the contents of a blob to a stream.
     *
     * @param outStream
     *            An <code>{@link OutputStream}</code> object that represents the target stream.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final void download(final OutputStream outStream) throws StorageException {
        this.download(outStream, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Downloads the contents of a blob to a stream using the specified request options and operation context.
     *
     * @param outStream
     *            An <code>OutputStream</code> object that represents the target stream.
     * @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 final void download(final OutputStream outStream, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

        ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.downloadToStreamImpl(
                null /* blobOffset */, null /* length */, outStream, accessCondition, options, opContext), options
                .getRetryPolicyFactory(), opContext);
    }

    /**
     * Downloads the contents of a blob to a stream.
     *
     * @param offset
     *            A <code>long</code> which represents the offset to use as the starting point for the source.
     * @param length
     *            A <code>Long</code> which represents the number of bytes to read or <code>null</code>.
     * @param outStream
     *            An <code>{@link OutputStream}</code> object that represents the target stream.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final void downloadRange(final long offset, final Long length, final OutputStream outStream)
            throws StorageException {
        this.downloadRange(offset, length, outStream, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Downloads the contents of a blob to a stream using the specified request options and operation context.
     *
     * @param offset
     *            A <code>long</code> which represents the offset to use as the starting point for the source.
     * @param length
     *            A <code>Long</code> which represents the number of bytes to read or <code>null</code>.
     * @param outStream
     *            An <code>{@link OutputStream}</code> object that represents the target stream.
     * @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 final void downloadRange(final long offset, final Long length, final OutputStream outStream,
            final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (offset < 0 || (length != null && length <= 0)) {
            throw new IndexOutOfBoundsException();
        }

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

        if (options.getUseTransactionalContentMD5() && (length != null && length > 4 * Constants.MB)) {
            throw new IllegalArgumentException(SR.INVALID_RANGE_CONTENT_MD5_HEADER);
        }

        ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.downloadToStreamImpl(offset, length, outStream, accessCondition, options, opContext),
                options.getRetryPolicyFactory(), opContext);
    }

    /**
     * Populates a blob's properties and metadata.
     * <p>
     * This method populates the blob's system properties and user-defined metadata. Before reading or modifying a
     * blob's properties or metadata, call this method or its overload to retrieve the latest values for the blob's
     * properties and metadata from the Microsoft Azure storage service.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final void downloadAttributes() throws StorageException {
        this.downloadAttributes(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Populates a blob's properties and metadata using the specified request options and operation context.
     * <p>
     * This method populates the blob's system properties and user-defined metadata. Before reading or modifying a
     * blob's properties or metadata, call this method or its overload to retrieve the latest values for the blob's
     * properties and metadata from the Microsoft Azure storage service.
     *
     * @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 final void downloadAttributes(final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest<CloudBlobClient, CloudBlob, Void> downloadAttributesImpl(
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlob, Void> getRequest = new StorageRequest<CloudBlobClient, CloudBlob, Void>(
                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.getBlobProperties(
                        blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context,
                        accessCondition, blob.snapshotID);
            }

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

            @Override
            public Void preProcessResponse(CloudBlob blob, 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(),
                        blob.getStorageUri(), blob.snapshotID);

                if (retrievedAttributes.getProperties().getBlobType() != blob.properties.getBlobType()) {
                    throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE, String.format(
                            SR.INVALID_BLOB_TYPE, blob.properties.getBlobType(), retrievedAttributes.getProperties()
                                    .getBlobType()), Constants.HeaderConstants.HTTP_UNUSED_306, null, null);
                }

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

                return null;
            }
        };

        return getRequest;
    }

    @DoesServiceRequest
    private final StorageRequest<CloudBlobClient, CloudBlob, Integer> downloadToStreamImpl(final Long blobOffset,
            final Long length, final OutputStream outStream, final AccessCondition accessCondition,
            final BlobRequestOptions options, OperationContext opContext) {

        final long startingOffset = blobOffset == null ? 0 : blobOffset;
        final boolean isRangeGet = blobOffset != null;
        final StorageRequest<CloudBlobClient, CloudBlob, Integer> getRequest = new StorageRequest<CloudBlobClient, CloudBlob, Integer>(
                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 {

                // The first time this is called, we have to set the length and blob offset. On retries, these will already have values and need not be called.
                if (this.getOffset() == null) {
                    this.setOffset(blobOffset);
                }

                if (this.getLength() == null) {
                    this.setLength(length);
                }

                AccessCondition tempCondition = (this.getETagLockCondition() != null) ? this.getETagLockCondition()
                        : accessCondition;

                return BlobRequest.getBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
                        options, context, tempCondition, blob.snapshotID, this.getOffset(), this.getLength(),
                        (options.getUseTransactionalContentMD5() && !this.getArePropertiesPopulated()));
            }

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

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

                if (!this.getArePropertiesPopulated()) {
                    final BlobAttributes retrievedAttributes = BlobResponse.getBlobAttributes(this.getConnection(),
                            blob.getStorageUri(), blob.snapshotID);

                    blob.properties = retrievedAttributes.getProperties();
                    blob.metadata = retrievedAttributes.getMetadata();
                    
                    // Need to store the Content MD5 in case we fail part way through.
                    // We would still need to verify the entire range.
                    String contentMD5 = this.getConnection().getHeaderField(Constants.HeaderConstants.CONTENT_MD5);
                    
                    if (!options.getDisableContentMD5Validation() && options.getUseTransactionalContentMD5()
                            && Utility.isNullOrEmpty(contentMD5)) {
                        throw new StorageException(StorageErrorCodeStrings.MISSING_MD5_HEADER, SR.MISSING_MD5,
                                Constants.HeaderConstants.HTTP_UNUSED_306, null, null);
                    }

                    this.setContentMD5(contentMD5);
                    this.setLockedETag(blob.properties.getEtag());
                    this.setArePropertiesPopulated(true);
                }

                // If the download fails and Get Blob needs to resume the download, going to the
                // same storage location is important to prevent a possible ETag mismatch.
                this.setRequestLocationMode(this.getResult().getTargetLocation() == StorageLocation.PRIMARY ? RequestLocationMode.PRIMARY_ONLY
                        : RequestLocationMode.SECONDARY_ONLY);
                return null;
            }

            @Override
            public Integer postProcessResponse(HttpURLConnection connection, CloudBlob blob, CloudBlobClient client,
                    OperationContext context, Integer storageObject) throws Exception {
                final Boolean validateMD5 = !options.getDisableContentMD5Validation()
                        && !Utility.isNullOrEmpty(this.getContentMD5());
                final String contentLength = connection.getHeaderField(Constants.HeaderConstants.CONTENT_LENGTH);
                final long expectedLength = Long.parseLong(contentLength);

                Logger.info(context, String.format(SR.CREATING_NETWORK_STREAM, expectedLength));
                final NetworkInputStream streamRef = new NetworkInputStream(connection.getInputStream(), expectedLength);

                try {
                    // writeToOutputStream will update the currentRequestByteCount on this request in case a retry
                    // is needed and download should resume from that point
                    final StreamMd5AndLength descriptor = Utility.writeToOutputStream(streamRef, outStream, -1, false,
                            validateMD5, context, options, this, this.getCurrentDescriptor());

                    // length was already checked by the NetworkInputStream, now check Md5
                    if (validateMD5 && !this.getContentMD5().equals(descriptor.getMd5())) {
                        throw new StorageException(StorageErrorCodeStrings.INVALID_MD5, String.format(
                                SR.BLOB_HASH_MISMATCH, this.getContentMD5(), descriptor.getMd5()),
                                Constants.HeaderConstants.HTTP_UNUSED_306, null, null);
                    }
                }
                finally {
                    // Close the stream and return. Closing an already closed stream is harmless. So its fine to try
                    // to drain the response and close the stream again in the executor.
                    streamRef.close();
                }

                return null;
            }

            @Override
            public void recoveryAction(OperationContext context) throws IOException {
                if (this.getETagLockCondition() == null && (!Utility.isNullOrEmpty(this.getLockedETag()))) {
                    AccessCondition etagLockCondition = new AccessCondition();
                    etagLockCondition.setIfMatch(this.getLockedETag());
                    if (accessCondition != null) {
                        etagLockCondition.setLeaseID(accessCondition.getLeaseID());
                    }
                    this.setETagLockCondition(etagLockCondition);
                }

                if (this.getCurrentRequestByteCount() > 0) {
                    this.setOffset(startingOffset + this.getCurrentRequestByteCount());
                    if (length != null) {
                        this.setLength(length - this.getCurrentRequestByteCount());
                    }
                }
            }
        };

        return getRequest;

    }

    /**
     * Downloads a range of bytes from the blob to the given byte buffer.
     *
     * @param blobOffset
     *            A <code>long</code> which represents the offset within the blob to begin downloading.
     * @param length
     *            A <code>Long</code> which represents the number of bytes to read.
     * @param buffer
     *            A <code>byte</code> array which represents the buffer to write to.
     * @param bufferOffset
     *            An <code>int</code> which represents the offset in the byte buffer to begin writing.
     * @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 used to track the execution of the operation.
     * @returns The total number of bytes read into the buffer.
     * 
     * @throws StorageException
     *             an exception representing any error which occurred during the operation.
     */
    @DoesServiceRequest
    protected final int downloadRangeInternal(final long blobOffset, final Long length, final byte[] buffer,
            final int bufferOffset, final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {

        if (bufferOffset < 0 || blobOffset < 0 || (length != null && length <= 0)) {
            throw new IndexOutOfBoundsException();
        }

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

        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

        if (options.getUseTransactionalContentMD5() && (length != null && length > 4 * Constants.MB)) {
            throw new IllegalArgumentException(SR.INVALID_RANGE_CONTENT_MD5_HEADER);
        }

        WrappedByteArrayOutputStream outputStream = new WrappedByteArrayOutputStream(buffer, bufferOffset);
        ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.downloadToStreamImpl(blobOffset, length, outputStream, accessCondition, options, opContext),
                options.getRetryPolicyFactory(), opContext);
        return outputStream.getPosition();
    }

    /**
     * Downloads a range of bytes from the blob to the given byte buffer.
     *
     * @param offset
     *            A <code>long</code> which represents the byte offset to use as the starting point for the source.
     * @param length
     *            A <code>Long</code> which represents the number of bytes to read or null.
     * @param buffer
     *            A <code>byte</code> array which represents the buffer to which the blob bytes are downloaded.
     * @param bufferOffset
     *            An <code>int</code> which represents the byte offset to use as the starting point for the target.
     * @returns The total number of bytes read into the buffer.
     * 
     * @throws StorageException
     */
    @DoesServiceRequest
    public final int downloadRangeToByteArray(final long offset, final Long length, final byte[] buffer,
            final int bufferOffset) throws StorageException {
        return this.downloadRangeToByteArray(offset, length, buffer, bufferOffset, null /* accessCondition */,
                null /* options */, null /* opContext */);
    }

    /**
     * Downloads a range of bytes from the blob to the given byte buffer, using the specified request options and
     * operation context.
     *
     * @param offset
     *            A <code>long</code> which represents the byte offset to use as the starting point for the source.
     * @param length
     *            A <code>Long</code> which represents the number of bytes to read or <code>null</code>.
     * @param buffer
     *            A <code>byte</code> array which represents the buffer to which the blob bytes are downloaded.
     * @param bufferOffset
     *            An <code>int</code> which represents the byte offset to use as the starting point for the target.
     * @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.
     * @returns The total number of bytes read into the buffer.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final int downloadRangeToByteArray(final long offset, final Long length, final byte[] buffer,
            final int bufferOffset, final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {

        Utility.assertNotNull("buffer", buffer);

        if (length != null) {
            if (length + bufferOffset > buffer.length) {
                throw new IndexOutOfBoundsException();
            }
        }

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

        opContext.initialize();

        return this.downloadRangeInternal(offset, length, buffer, bufferOffset, accessCondition, options, opContext);
    }

    /**
     * Downloads a range of bytes from the blob to the given byte buffer.
     *
     * @param buffer
     *            A <code>byte</code> array which represents the buffer to which the blob bytes are downloaded.
     * @param bufferOffset
     *            An <code>int</code> which represents the byte offset to use as the starting point for the target.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final int downloadToByteArray(final byte[] buffer, final int bufferOffset) throws StorageException {
        return this
                .downloadToByteArray(buffer, bufferOffset, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Downloads a range of bytes from the blob to the given byte buffer, using the specified request options and
     * operation context.
     *
     * @param buffer
     *            A <code>byte</code> array which represents the buffer to which the blob bytes are downloaded.
     * @param bufferOffset
     *            A <code>long</code> which represents the byte offset to use as the starting point for the target.
     * @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 final int downloadToByteArray(final byte[] buffer, final int bufferOffset,
            final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {

        Utility.assertNotNull("buffer", buffer);
        if (bufferOffset < 0) {
            throw new IndexOutOfBoundsException();
        }

        if (bufferOffset >= buffer.length) {
            throw new IndexOutOfBoundsException();
        }

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

        WrappedByteArrayOutputStream outputStream = new WrappedByteArrayOutputStream(buffer, bufferOffset);
        ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.downloadToStreamImpl(null, null, outputStream, accessCondition, options, opContext),
                options.getRetryPolicyFactory(), opContext);
        return outputStream.getPosition();
    }

    /**
     * Uploads a blob from data in a byte array. If the blob already exists on the service, it will be overwritten.
     *
     * @param buffer
     *            A <code>byte</code> array which represents the data to write to the blob.
     * @param offset
     *            A <code>int</code> which represents the offset of the byte array from which to start the data upload.
     * @param length
     *            An <code>int</code> which represents the number of bytes to upload from the input buffer.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws IOException
     */
    public void uploadFromByteArray(final byte[] buffer, final int offset, final int length) throws StorageException,
            IOException {
        uploadFromByteArray(buffer, offset, length, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Uploads a blob from data in a byte array. If the blob already exists on the service, it will be overwritten.
     *
     * @param buffer
     *            A <code>byte</code> array which represents the data to write to the blob.
     * @param offset
     *            A <code>int</code> which represents the offset of the byte array from which to start the data upload.
     * @param length
     *            An <code>int</code> which represents the number of bytes to upload from the input buffer.
     * @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 uploadFromByteArray(final byte[] buffer, final int offset, final int length,
            final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException, IOException {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer, offset, length);
        this.upload(inputStream, length, accessCondition, options, opContext);
        inputStream.close();
    }

    /**
     * Uploads a blob from a file. If the blob already exists on the service, it will be overwritten.
     *
     * @param path
     *            A <code>String</code> which represents the path to the file to be uploaded.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws IOException
     */
    public void uploadFromFile(final String path) throws StorageException, IOException {
        uploadFromFile(path, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Uploads a blob from a file. If the blob already exists on the service, it will be overwritten.
     *
     * @param path
     *            A <code>String</code> which represents the path to the file to be uploaded.
     * @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 uploadFromFile(final String path, final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException, IOException {
        File file = new File(path);
        long fileLength = file.length();
        InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
        this.upload(inputStream, fileLength, accessCondition, options, opContext);
        inputStream.close();
    }

    /**
     * Downloads a blob, storing the contents in a file.
     *
     * @param path
     *            A <code>String</code> which represents the path to the file that will be created with the contents of
     *            the blob.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws IOException
     */
    public void downloadToFile(final String path) throws StorageException, IOException {
        downloadToFile(path, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Downloads a blob, storing the contents in a file.
     *
     * @param path
     *            A <code>String</code> which represents the path to the file that will be created with the contents 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.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws IOException
     */
    public void downloadToFile(final String path, final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException, IOException {
        OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(path));
        try {
            this.download(outputStream, accessCondition, options, opContext);
            outputStream.close();
        }
        catch (StorageException e) {
            deleteEmptyFileOnException(outputStream, path);
            throw e;
        }
        catch (IOException e) {
            deleteEmptyFileOnException(outputStream, path);
            throw e;
        }
    }

    /**
     * Helper to delete an empty file in the case of an exception
     * 
     * @param outputStream
     * @param path
     * @throws IOException
     */
    private void deleteEmptyFileOnException(OutputStream outputStream, String path) {
        try {
            outputStream.close();
            File fileToDelete = new File(path);
            fileToDelete.delete();
        }
        catch (Exception e) {
            // Best effort delete.
        }
    }

    /**
     * Checks to see if the blob exists.
     *
     * @return <code>true</code> if the blob exists, otherwise <code>false</code>.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final boolean exists() throws StorageException {
        return this.exists(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Checks to see if the blob exists, using the specified request options and operation context.
     *
     * @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 <code>true</code> if the blob exists, other wise <code>false</code>.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final boolean exists(final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        return this.exists(false /* primaryOnly */, accessCondition, options, opContext);
    }

    @DoesServiceRequest
    private final 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, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest<CloudBlobClient, CloudBlob, Boolean> existsImpl(final boolean primaryOnly,
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlob, Boolean> getRequest = new StorageRequest<CloudBlobClient, CloudBlob, Boolean>(
                options, this.getStorageUri()) {
            @Override
            public void setRequestLocationMode() {
                this.setRequestLocationMode(primaryOnly ? RequestLocationMode.PRIMARY_ONLY
                        : RequestLocationMode.PRIMARY_OR_SECONDARY);
            }

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

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

            @Override
            public Boolean preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) {
                    final BlobAttributes retrievedAttributes = BlobResponse.getBlobAttributes(this.getConnection(),
                            blob.getStorageUri(), blob.snapshotID);
                    blob.properties = retrievedAttributes.getProperties();
                    blob.metadata = retrievedAttributes.getMetadata();
                    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 blob using the specified group policy identifier and operation context.
     * Note this does not contain the leading "?".
     *
     * @param policy
     *            A <code>SharedAccessPolicy</code> object that represents the access policy for the shared access
     *            signature.
     * @param groupPolicyIdentifier
     *            A <code>String</code> that represents the container-level access policy.
     *
     * @return A <code>String</code> that represents the shared access signature.
     *
     * @throws IllegalArgumentException
     *             If the credentials are invalid or the blob is a snapshot.
     * @throws InvalidKeyException
     *             If the credentials are invalid.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy, final String groupPolicyIdentifier)
            throws InvalidKeyException, StorageException {

        return this.generateSharedAccessSignature(policy, null /* headers */, groupPolicyIdentifier);
    }
    
    /**
     * Returns a shared access signature for the blob using the specified group policy identifier and operation context.
     * Note this does not contain the leading "?".
     *
     * @param policy
     *            A <code>SharedAccessPolicy</code> object that represents the access policy for the shared access
     *            signature.
     * @param headers
     *            A <code>{@link SharedAccessBlobHeaders}</code> object that represents the optional header values to
     *            set for a blob accessed with this shared access signature.
     * @param groupPolicyIdentifier
     *            A <code>String</code> that represents the container-level access policy.
     *
     * @return A <code>String</code> that represents the shared access signature.
     *
     * @throws IllegalArgumentException
     *             If the credentials are invalid or the blob is a snapshot.
     * @throws InvalidKeyException
     *             If the credentials are invalid.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy, final SharedAccessBlobHeaders headers,
            final String groupPolicyIdentifier)
            throws InvalidKeyException, StorageException {

        return this.generateSharedAccessSignature(policy, headers, groupPolicyIdentifier,
                null /* IP range */, null /* protocols */);
    }
    
    /**
     * Returns a shared access signature for the blob using the specified group policy identifier and operation context.
     * Note this does not contain the leading "?".
     *
     * @param policy
     *            A <code>{@link SharedAccessPolicy}</code> object that represents the access policy for the shared
     *            access signature.
     * @param headers
     *            A <code>{@link SharedAccessBlobHeaders}</code> object that represents the optional header values to
     *            set for a blob accessed with this shared access signature.
     * @param groupPolicyIdentifier
     *            A <code>String</code> that 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> that represents the shared access signature.
     *
     * @throws IllegalArgumentException
     *             If the credentials are invalid or the blob is a snapshot.
     * @throws InvalidKeyException
     *             If the credentials are invalid.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public String generateSharedAccessSignature(
            final SharedAccessBlobPolicy policy, final SharedAccessBlobHeaders headers,
            final String groupPolicyIdentifier, final IPRange ipRange, final SharedAccessProtocols protocols)
            throws InvalidKeyException, StorageException {
        
        if (!StorageCredentialsHelper.canCredentialsSignRequest(this.blobServiceClient.getCredentials())) {
            throw new IllegalArgumentException(SR.CANNOT_CREATE_SAS_WITHOUT_ACCOUNT_KEY);
        }
        
        if (this.isSnapshot()) {
            throw new IllegalArgumentException(SR.CANNOT_CREATE_SAS_FOR_SNAPSHOTS);
        }

        final String resourceName = this.getCanonicalName(true);

        final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForBlobAndFile(
                policy, headers, groupPolicyIdentifier, resourceName, ipRange, protocols, this.blobServiceClient);

        final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForBlobAndFile(
                policy, headers, groupPolicyIdentifier, "b", ipRange, protocols, signature);

        return builder.toString();
    }

    /**
     * Returns the canonical name of the blob in the format of
     * <i>/&lt;service-name&gt;/&lt;account-name&gt;/&lt;container-name&gt;/&lt;blob-name&gt;</i>.
     * <p>
     * This format is used for Shared Access operations.
     *
     * @param ignoreSnapshotTime
     *            <code>true</code> if the snapshot time is ignored; otherwise, <code>false</code>.
     *
     * @return The canonical name in the format of <i>/&lt;service-name&gt;/&lt;account-name&gt;
     *         /&lt;container-name&gt;/&lt;blob-name&gt;</i>.
     */
    String getCanonicalName(final boolean ignoreSnapshotTime) {
        StringBuilder canonicalName = new StringBuilder("/");
        canonicalName.append(SR.BLOB);
        
        if (this.blobServiceClient.isUsePathStyleUris()) {
            canonicalName.append(this.getUri().getRawPath());
        }
        else {
            canonicalName.append(PathUtility.getCanonicalPathFromCredentials(
                    this.blobServiceClient.getCredentials(), this.getUri().getRawPath()));
        }

        if (!ignoreSnapshotTime && this.snapshotID != null) {
            canonicalName.append("?snapshot=");
            canonicalName.append(this.snapshotID);
        }

        return canonicalName.toString();
    }

    /**
     * Returns the blob's container.
     *
     * @return A {@link CloudBlobContainer} object that represents the container of the blob.
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    @Override
    public final CloudBlobContainer getContainer() throws StorageException, URISyntaxException {
        if (this.container == null) {
            final StorageUri containerURI = PathUtility.getContainerURI(this.getStorageUri(),
                    this.blobServiceClient.isUsePathStyleUris());
            this.container = new CloudBlobContainer(containerURI, this.blobServiceClient.getCredentials());
        }

        return this.container;
    }

    /**
     * Returns the metadata for the blob.
     *
     * @return A <code>java.util.HashMap</code> object that represents the metadata for the blob.
     */
    public final HashMap<String, String> getMetadata() {
        return this.metadata;
    }

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

    /**
     * Returns the blob item's parent.
     *
     * @return A {@link CloudBlobDirectory} object that represents the parent directory for the blob.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    @Override
    public final CloudBlobDirectory getParent() throws URISyntaxException, StorageException {
        if (this.parent == null) {
            final String parentName = getParentNameFromURI(this.getStorageUri(),
                    this.blobServiceClient.getDirectoryDelimiter(), this.getContainer());

            if (parentName != null) {
                StorageUri parentURI = PathUtility.appendPathToUri(this.container.getStorageUri(), parentName);
                this.parent = new CloudBlobDirectory(parentURI, parentName, this.blobServiceClient, this.getContainer());
            }
        }
        return this.parent;
    }

    /**
     * Returns the blob's properties.
     *
     * @return A {@link BlobProperties} object that represents the properties of the blob.
     */
    public final BlobProperties getProperties() {
        return this.properties;
    }

    /**
     * Returns the blob's copy state.
     *
     * @return A {@link CopyState} object that represents the copy state of the blob.
     */
    public CopyState getCopyState() {
        return this.properties.getCopyState();
    }

    /**
     * Returns the blob's URI for both the primary and secondary locations, including query string information if the blob is a snapshot.
     *
     * @return A {@link StorageUri} object containing the blob's URIs for both the primary and secondary locations, 
     *         including snapshot query information if the blob is a snapshot.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    public final StorageUri getSnapshotQualifiedStorageUri() throws URISyntaxException, StorageException {
        if (this.isSnapshot()) {
            return PathUtility.addToQuery(this.getStorageUri(),
                    String.format("snapshot=%s", this.snapshotID));
        }

        return this.getStorageUri();
    }
    
    /**
     * Returns the absolute URI to the blob, including query string information if the blob is a snapshot.
     *
     * @return A <code>java.net.URI</code> object specifying the absolute URI to the blob,
     *         including snapshot query information if the blob is a snapshot.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    public final URI getSnapshotQualifiedUri() throws URISyntaxException, StorageException {
        if (this.isSnapshot()) {
            return PathUtility.addToQuery(this.getUri(), String.format("snapshot=%s", this.snapshotID));
        }

        return this.getUri();
    }

    /**
     * Returns the snapshot and/or shared access signature qualified URI for this blob.
     *
     * @deprecated use {@link #getSnapshotQualifiedStorageUri()} instead.
     *
     * @return A {@link StorageUri} object that represents the snapshot or shared access signature.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    @Deprecated
    public final StorageUri getQualifiedStorageUri() throws URISyntaxException, StorageException {
        if (this.isSnapshot()) {
            StorageUri snapshotQualifiedUri = PathUtility.addToQuery(this.getStorageUri(),
                    String.format("snapshot=%s", this.snapshotID));
            return this.blobServiceClient.getCredentials().transformUri(snapshotQualifiedUri);
        }
        return this.blobServiceClient.getCredentials().transformUri(this.getStorageUri());
    }

    /**
     * Returns the snapshot or shared access signature qualified URI for this blob.
     *
     * @deprecated use {@link #getSnapshotQualifiedUri()} instead.
     *
     * @return A <code>java.net.URI</code> object that represents the snapshot or shared access signature.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    @Deprecated
    public final URI getQualifiedUri() throws URISyntaxException, StorageException {
        if (this.isSnapshot()) {
            return PathUtility.addToQuery(this.getUri(), String.format("snapshot=%s", this.snapshotID));
        }
        return this.blobServiceClient.getCredentials().transformUri(this.getUri());
    }

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

    /**
     * Gets the Blob Snapshot ID.
     *
     * @return A <code>String</code> which represents the Blob Snapshot ID.
     */
    public final String getSnapshotID() {
        return this.snapshotID;
    }

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

    /**
     * Gets the number of bytes to buffer when writing to a {@link BlobOutputStream} (block and page blobs).
     *
     * @return
     *         A <code>int</code> which represents the number of bytes to buffer or the size of a block, in bytes.
     */
    public final int getStreamWriteSizeInBytes() {
        return this.streamWriteSizeInBytes;
    }

    /**
     * Returns the minimum read size when using a {@link BlobInputStream}.
     *
     * @return A <code>int</code> which represents the minimum read size, in bytes, when using a {@link BlobInputStream}
     *         object.
     */
    public final int getStreamMinimumReadSizeInBytes() {
        return this.streamMinimumReadSizeInBytes;
    }

    /**
     * Returns the transformed URI for the resource if the given credentials require transformation.
     *
     * @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 StorageUri} object that represents the transformed URI.
     *
     * @throws IllegalArgumentException
     *             If the URI is not absolute.
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     *             If the resource URI is invalid.
     */
    protected final StorageUri getTransformedAddress(final OperationContext opContext) throws URISyntaxException,
            StorageException {
        return this.blobServiceClient.getCredentials().transformUri(this.getStorageUri(), opContext);
    }

    /**
     * Returns the URI for this blob.
     *
     * @return A <code>java.net.URI</code> object that represents the URI for the blob.
     */
    @Override
    public final URI getUri() {
        return this.storageUri.getPrimaryUri();
    }

    /**
     * Indicates whether this blob is a snapshot.
     *
     * @return <code>true</code> if the blob is a snapshot, otherwise <code>false</code>.
     *
     * @see DeleteSnapshotsOption
     */
    public final boolean isSnapshot() {
        return this.snapshotID != null;
    }

    /**
     * Opens a blob input stream to download the blob.
     * <p>
     * Use {@link #setStreamMinimumReadSizeInBytes(int)} to configure the read size.
     *
     * @return An <code>InputStream</code> object that represents the stream to use for reading from the blob.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final BlobInputStream openInputStream() throws StorageException {
        return this.openInputStream(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Opens a blob input stream to download the blob using the specified request options and operation context.
     * <p>
     * Use {@link #setStreamMinimumReadSizeInBytes(int)} to configure the read size.
     *
     * @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>InputStream</code> object that represents the stream to use for reading from the blob.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final BlobInputStream openInputStream(final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        assertNoWriteOperationForSnapshot();

        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient, 
                false /* setStartTime */);

        return new BlobInputStream(this, accessCondition, 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 HashMap<String, String[]> queryParameters = PathUtility.parseQueryString(completeUri.getQuery());

        final String[] snapshotIDs = queryParameters.get(BlobConstants.SNAPSHOT);
        if (snapshotIDs != null && snapshotIDs.length > 0) {
            this.snapshotID = snapshotIDs[0];
        }
        
        final StorageCredentialsSharedAccessSignature parsedCredentials = 
                SharedAccessSignatureHelper.parseQuery(queryParameters);

        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.getBlobNameFromURI(this.storageUri.getPrimaryUri(), usePathStyleUris);
        }
        catch (final URISyntaxException e) {
            throw Utility.generateNewUnexpectedStorageException(e);
        }
    }

    /**
     * Releases the lease on the blob.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is
     *            required to be set on the AccessCondition.
     *
     * @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 blob using the specified request options and operation context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob.The LeaseID is
     *            required to be set on the AccessCondition.
     * @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 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, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest<CloudBlobClient, CloudBlob, Void> releaseLeaseImpl(final AccessCondition accessCondition,
            final BlobRequestOptions options) {
        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 {
                return BlobRequest.leaseBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
                        options, context, accessCondition, LeaseAction.RELEASE, 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(CloudBlob blob, CloudBlobClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                updateEtagAndLastModifiedFromResponse(this.getConnection());

                blob.properties.setLeaseStatus(LeaseStatus.UNLOCKED);
                return null;
            }
        };

        return putRequest;
    }

    /**
     * Renews an existing lease.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is
     *            required to be set on the AccessCondition.
     *
     * @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 using the specified request options and operation context.
     *
     * @param accessCondition
     *            An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is
     *            required to be set on the AccessCondition.
     * @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 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, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest<CloudBlobClient, CloudBlob, Void> renewLeaseImpl(final AccessCondition accessCondition,
            final BlobRequestOptions options) {
        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 {
                return BlobRequest.leaseBlob(blob.getTransformedAddress(context).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(CloudBlob blob, CloudBlobClient client, OperationContext context)
                    throws Exception {
                if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
                    this.setNonExceptionedRetryableFailure(true);
                    return null;
                }

                updateEtagAndLastModifiedFromResponse(this.getConnection());

                return null;
            }
        };

        return putRequest;
    }

    protected StorageRequest<CloudBlobClient, CloudBlob, Void> uploadBlobTierImpl(final String blobTierString, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlob, Void> setTierRequest = new StorageRequest<CloudBlobClient, CloudBlob, Void>(
                options, this.getStorageUri()) {

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

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

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

                blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
                this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection()));
                blob.getProperties().setBlobTierInferred(false);
                if (blob.getProperties().getBlobType() == BlobType.BLOCK_BLOB) {
                    // For standard accounts when rehydrating a blob from archive, the status code will be 202 instead of 200.
                    StandardBlobTier standardBlobTier = StandardBlobTier.parse(blobTierString);
                    blob.getProperties().setRehydrationStatus(null);
                    if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) {
                        blob.getProperties().setStandardBlobTier(standardBlobTier);
                    }
                    else if (standardBlobTier.equals(StandardBlobTier.COOL)) {
                        blob.getProperties().setStandardBlobTier(StandardBlobTier.ARCHIVE);
                    }
                    else if (standardBlobTier.equals(StandardBlobTier.HOT)) {
                        blob.getProperties().setStandardBlobTier(StandardBlobTier.ARCHIVE);
                    }
                }

                return null;
            }

        };

        return setTierRequest;
    }

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

    /**
     * Sets the metadata for the blob.
     *
     * @param metadata
     *            A <code>java.util.HashMap</code> object that contains the metadata being assigned to the blob.
     */
    public final void setMetadata(final HashMap<String, String> metadata) {
        this.metadata = metadata;
    }

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

    /**
     * Sets the blob snapshot ID.
     *
     * @param snapshotID
     *            A <code>String</code> that represents the snapshot ID being assigned to the blob.
     */
    protected final void setSnapshotID(final String snapshotID) {
        this.snapshotID = snapshotID;
    }

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

    /**
     * Sets the number of bytes to buffer when writing to a {@link BlobOutputStream} (block and page blobs).
     *
     * @param streamWriteSizeInBytes
     *            An <code>int</code> that represents the number of bytes to buffer or the size of a block, in bytes.
     */
    public abstract void setStreamWriteSizeInBytes(int streamWriteSizeInBytes);

    /**
     * Sets the minimum read size when using a {@link BlobInputStream}.
     *
     * @param minimumReadSize
     *            An <code>int</code> that represents the minimum block size, in bytes, for reading from a blob while
     *            using a {@link BlobInputStream} object. Must be greater than or equal to 16 KB.
     * @throws IllegalArgumentException
     *             If <code>minimumReadSize</code> is less than 16 KB.
     */
    public void setStreamMinimumReadSizeInBytes(final int minimumReadSize) {
        if (minimumReadSize < 16 * Constants.KB) {
            throw new IllegalArgumentException("MinimumReadSize");
        }

        this.streamMinimumReadSizeInBytes = minimumReadSize;
    }

    protected void updateEtagAndLastModifiedFromResponse(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());
        }
    }

    protected void updateLengthFromResponse(HttpURLConnection request) {
        final String xContentLengthHeader = request.getHeaderField(BlobConstants.CONTENT_LENGTH_HEADER);
        if (!Utility.isNullOrEmpty(xContentLengthHeader)) {
            this.getProperties().setLength(Long.parseLong(xContentLengthHeader));
        }
    }

    /**
     * Uploads the source stream data to the blob. If the blob already exists on the service, it will be overwritten.
     *
     * @param sourceStream
     *            An <code>InputStream</code> object that represents the source stream to upload.
     *
     * @param length
     *            An <code>long</code> that represents the length of the stream data in bytes, or -1 if unknown. The
     *            length must be greater than zero and a
     *            multiple of 512 for page blobs.
     *
     * @throws IOException
     *             If an I/O exception occurred.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     *
     */
    @DoesServiceRequest
    public abstract void upload(InputStream sourceStream, long length) throws StorageException, IOException;

    /**
     * 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 <code>InputStream</code> object that represents the source stream to upload.
     * @param length
     *            The length of the stream data in bytes, or -1 if unknown. The length must be greater than zero and a
     *            multiple of 512 for page blobs.
     * @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 exception occurred.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public abstract void upload(InputStream sourceStream, long length, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException;


    /**
     * Uploads the blob's metadata to the storage service.
     * <p>
     * Use {@link CloudBlob#downloadAttributes} to retrieve the latest values for the blob's properties and metadata
     * from the Microsoft Azure storage service.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final void uploadMetadata() throws StorageException {
        this.uploadMetadata(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Uploads the blob's metadata to the storage service using the specified lease ID, request options, and operation
     * context.
     * <p>
     * Use {@link CloudBlob#downloadAttributes} to retrieve the latest values for the blob's properties and metadata
     * from the Microsoft Azure storage service.
     *
     * @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 final void uploadMetadata(final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        assertNoWriteOperationForSnapshot();

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest<CloudBlobClient, CloudBlob, Void> uploadMetadataImpl(final AccessCondition accessCondition,
            final BlobRequestOptions options) {
        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 {
                return BlobRequest.setBlobMetadata(blob.getTransformedAddress(context)
                        .getUri(this.getCurrentLocation()), options, context, accessCondition);
            }

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

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

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

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

        return putRequest;
    }

    /**
     * Updates the blob's properties to the storage service.
     * <p>
     * Use {@link CloudBlob#downloadAttributes} to retrieve the latest values for the blob's properties and metadata
     * from the Microsoft Azure storage service.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final void uploadProperties() throws StorageException {
        this.uploadProperties(null /* accessCondition */, null /* options */, null /*opContext */);
    }

    /**
     * Updates the blob's properties using the specified lease ID, request options, and operation context.
     * <p>
     * Use {@link CloudBlob#downloadAttributes} to retrieve the latest values for the blob's properties and metadata
     * from the Microsoft Azure storage service.
     *
     * @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 final void uploadProperties(final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        assertNoWriteOperationForSnapshot();

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest<CloudBlobClient, CloudBlob, Void> uploadPropertiesImpl(
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        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 {
                return BlobRequest.setBlobProperties(
                        blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context,
                        accessCondition, blob.properties);
            }

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

            }

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

                blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
                return null;
            }
        };

        return putRequest;
    }

    /**
     * Retrieves the parent name for a blob URI.
     *
     * @param resourceAddress
     *            A {@link StorageUri} object which represents the resource URI.
     * @param delimiter
     *            A <code>String</code> which specifies the directory delimiter to use.
     * @param container
     *            A {@link CloudBlobContainer} object which represents the blob container.
     *
     * @return A <code>String</code> which represents the parent address for a blob URI.
     *
     * @throws URISyntaxException
     */
    protected static String getParentNameFromURI(final StorageUri resourceAddress, final String delimiter,
            final CloudBlobContainer container) throws URISyntaxException {
        Utility.assertNotNull("resourceAddress", resourceAddress);
        Utility.assertNotNull("container", container);
        Utility.assertNotNullOrEmpty("delimiter", delimiter);

        String containerName = container.getName() + "/";

        String relativeURIString = Utility.safeRelativize(container.getStorageUri().getPrimaryUri(),
                resourceAddress.getPrimaryUri());

        if (relativeURIString.endsWith(delimiter)) {
            relativeURIString = relativeURIString.substring(0, relativeURIString.length() - delimiter.length());
        }

        String parentName;

        if (Utility.isNullOrEmpty(relativeURIString)) {
            // Case 1 /<ContainerName>[Delimiter]*? => /<ContainerName>
            // Parent of container is container itself
            parentName = null;
        }
        else {
            final int lastDelimiterDex = relativeURIString.lastIndexOf(delimiter);

            if (lastDelimiterDex < 0) {
                // Case 2 /<Container>/<folder>
                // Parent of a folder is container
                parentName = "";
            }
            else {
                // Case 3 /<Container>/<folder>/[<subfolder>/]*<BlobName>
                // Parent of blob is folder
                parentName = relativeURIString.substring(0, lastDelimiterDex + delimiter.length());
                if (parentName != null && parentName.equals(containerName)) {
                    parentName = "";
                }
            }
        }

        return parentName;
    }
} 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 final class CloudPageBlob extends CloudBlob {
    /**
     * Creates an instance of the <code>CloudPageBlob</code> class using the specified absolute URI and storage service
     * client.
     * 
     * @param blobAbsoluteUri
     *            A <code>java.net.URI</code> object which represents the absolute URI to the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public CloudPageBlob(final URI blobAbsoluteUri) throws StorageException {
        this(new StorageUri(blobAbsoluteUri));
    }

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

    /**
     * Creates an instance of the <code>CloudPageBlob</code> class by copying values from another page blob.
     * 
     * @param otherBlob
     *            A <code>CloudPageBlob</code> object which represents the page blob to copy.
     */
    public CloudPageBlob(final CloudPageBlob otherBlob) {
        super(otherBlob);
    }
    
    /**
     * Creates an instance of the <code>CloudPageBlob</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 CloudPageBlob(final URI blobAbsoluteUri, final StorageCredentials credentials) throws StorageException {
        this(new StorageUri(blobAbsoluteUri), credentials);
    }

    /**
     * Creates an instance of the <code>CloudPageBlob</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 CloudPageBlob(final URI blobAbsoluteUri, final String snapshotID, final StorageCredentials credentials)
            throws StorageException {
        this(new StorageUri(blobAbsoluteUri), snapshotID, credentials);
    }
    
    /**
     * Creates an instance of the <code>CloudPageBlob</code> class using the specified absolute StorageUri and credentials.
     * 
     * @param blobAbsoluteUri
     *            A {@link StorageUri} 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 CloudPageBlob(final StorageUri blobAbsoluteUri, final StorageCredentials credentials) throws StorageException {
        this(blobAbsoluteUri, null /* snapshotID */, credentials);
    }

    /**
     * Creates an instance of the <code>CloudPageBlob</code> class using the specified absolute StorageUri, snapshot
     * ID, and credentials.
     * 
     * @param blobAbsoluteUri
     *            A {@link StorageUri} 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 CloudPageBlob(final StorageUri blobAbsoluteUri, final String snapshotID, final StorageCredentials credentials)
            throws StorageException {
        super(BlobType.PAGE_BLOB, blobAbsoluteUri, snapshotID, credentials);
    }
    
    /**
     * Creates an instance of the <code>CloudPageBlob</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 CloudPageBlob(String blobName, String snapshotID, CloudBlobContainer container)
            throws URISyntaxException {
        super(BlobType.PAGE_BLOB, blobName, snapshotID, container);
    }
    
    /**
     * Requests the service to start copying a blob's contents, properties, and metadata to a new blob.
     *
     * @param sourceBlob
     *            A <code>CloudPageBlob</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 CloudPageBlob sourceBlob) throws StorageException, URISyntaxException {
        return this.startCopy(sourceBlob, null /* sourceAccessCondition */,
                null /* destinationAccessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Requests the service to start copying a blob's contents, properties, and metadata to a new blob, using the
     * specified access conditions, lease ID, request options, and operation context.
     *
     * @param sourceBlob
     *            A <code>CloudPageBlob</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 CloudPageBlob sourceBlob, final AccessCondition sourceAccessCondition,
                                  final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException, URISyntaxException {
        return this.startCopy(sourceBlob, null /* premiumBlobTier */, sourceAccessCondition, destinationAccessCondition, options, opContext);
    }

    /**
     * Requests the service to start copying a blob's contents, properties, and metadata to a new blob, using the
     * specified blob tier, access conditions, lease ID, request options, and operation context.
     *
     * @param sourceBlob
     *            A <code>CloudPageBlob</code> object that represents the source blob to copy.
     * @param premiumBlobTier
     *            A {@link PremiumPageBlobTier} object which represents the tier of the blob.
     * @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 CloudPageBlob sourceBlob, final PremiumPageBlobTier premiumBlobTier, 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, premiumBlobTier, sourceAccessCondition, destinationAccessCondition, options, opContext);
    }

    /**
     * Requests the service to start an incremental copy of another page blob's contents, properties, and metadata
     * to this blob.
     *
     * @param sourceSnapshot
     *            A <code>CloudPageBlob</code> object that represents the source blob to copy. Must be a snapshot.
     *
     * @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 startIncrementalCopy(final CloudPageBlob sourceSnapshot) throws StorageException, URISyntaxException {
        final UriQueryBuilder builder = new UriQueryBuilder();
        builder.add(Constants.QueryConstants.SNAPSHOT, sourceSnapshot.snapshotID);
        URI sourceUri = builder.addToURI(sourceSnapshot.getTransformedAddress(null).getPrimaryUri());

        return this.startIncrementalCopy(sourceUri, null /* destinationAccessCondition */,
                null /* options */, null /* opContext */);
    }

    /**
     * Requests the service to start an incremental copy of another page blob's contents, properties, and metadata
     * to this blob.
     *
     * @param sourceSnapshot
     *            A <code>CloudPageBlob</code> object that represents the source blob to copy. Must be a snapshot.
     *
     * @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 startIncrementalCopy(final URI sourceSnapshot) throws StorageException, URISyntaxException {
        return this.startIncrementalCopy(sourceSnapshot, null /* destinationAccessCondition */,
                null /* options */, null /* opContext */);
    }

    /**
     * Requests the service to start copying a blob's contents, properties, and metadata to a new blob, using the
     * specified access conditions, lease ID, request options, and operation context.
     *
     * @param sourceSnapshot
     *            A <code>CloudPageBlob</code> object that represents the source blob to copy. Must be a snapshot.
     * @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 startIncrementalCopy(final CloudPageBlob sourceSnapshot, 
            final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException, URISyntaxException {
        final UriQueryBuilder builder = new UriQueryBuilder();
        builder.add(Constants.QueryConstants.SNAPSHOT, sourceSnapshot.snapshotID);

        URI sourceUri = builder.addToURI(sourceSnapshot.getTransformedAddress(null).getPrimaryUri());
        return this.startIncrementalCopy(sourceUri, destinationAccessCondition, options, opContext);
    }

    /**
     * Requests the service to start copying a blob's contents, properties, and metadata to a new blob, using the
     * specified access conditions, lease ID, request options, and operation context.
     *
     * @param sourceSnapshot
     *            A <code>CloudPageBlob</code> object that represents the source blob to copy. Must be a snapshot.
     * @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 startIncrementalCopy(final URI sourceSnapshot,
            final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException, URISyntaxException {
        Utility.assertNotNull("sourceSnapshot", sourceSnapshot);
        this.assertNoWriteOperationForSnapshot();
        
        if (opContext == null) {
            opContext = new OperationContext();
        }

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.startCopyImpl(sourceSnapshot, true /* incrementalCopy */, null /* premiumPageBlobTier */, null /* sourceAccesCondition */,
                        destinationAccessCondition, options),
                options.getRetryPolicyFactory(), opContext);
    }

    /**
     * Clears pages from a page blob.
     * <p>
     * Calling <code>clearPages</code> releases the storage space used by the specified pages. Pages that have been
     * cleared are no longer tracked as part of the page blob, and no longer incur a charge against the storage account.
     * 
     * @param offset
     *            The offset, in bytes, at which to begin clearing pages. This value must be a multiple of 512.
     * @param length
     *            The length, in bytes, of the data range to be cleared. This value must be a multiple of 512.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void clearPages(final long offset, final long length) throws StorageException {
        this.clearPages(offset, length, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Clears pages from a page blob using the specified lease ID, request options, and operation context.
     * <p>
     * Calling <code>clearPages</code> releases the storage space used by the specified pages. Pages that have been
     * cleared are no longer tracked as part of the page blob, and no longer incur a charge against the storage account.
     * 
     * @param offset
     *            A <code>long</code> which represents the offset, in bytes, at which to begin clearing pages. This
     *            value must be a multiple of 512.
     * @param length
     *            A <code>long</code> which represents the length, in bytes, of the data range to be cleared. This value
     *            must be a multiple of 512.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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 clearPages(final long offset, final long length, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException {
        if (offset % Constants.PAGE_SIZE != 0) {
            throw new IllegalArgumentException(SR.INVALID_PAGE_START_OFFSET);
        }

        if (length % Constants.PAGE_SIZE != 0) {
            throw new IllegalArgumentException(SR.INVALID_PAGE_BLOB_LENGTH);
        }

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

        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient);
        PageRange range = new PageRange(offset, offset + length - 1);

        this.putPagesInternal(range, PageOperationType.CLEAR, null, length, null, accessCondition, options, opContext);
    }

    /**
     * Creates a page blob. If the blob already exists, this will replace it. To instead throw an error if the blob 
     * already exists, use the {@link #create(long, AccessCondition, BlobRequestOptions, OperationContext)}
     * overload with {@link AccessCondition#generateIfNotExistsCondition()}.
     * @param length
     *            A <code>long</code> which represents the size, in bytes, of the page blob.
     * 
     * @throws IllegalArgumentException
     *             If the length is not a multiple of 512.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void create(final long length) throws StorageException {
        this.create(length, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Creates a page blob using the specified request options and operation context. If the blob already exists,
     * this will replace it. To instead throw an error if the blob already exists, use
     * {@link AccessCondition#generateIfNotExistsCondition()}.
     *
     * @param length
     *            A <code>long</code> which represents the size, in bytes, of the page blob.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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 IllegalArgumentException
     *             If the length is not a multiple of 512.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void create(final long length, final AccessCondition accessCondition, BlobRequestOptions options,
                       OperationContext opContext) throws StorageException {
        this.create(length, null /* premiumBlobTier */, accessCondition, options, opContext);
    }

    /**
     * Creates a page blob using the specified request options and operation context. If the blob already exists, 
     * this will replace it. To instead throw an error if the blob already exists, use 
     * {@link AccessCondition#generateIfNotExistsCondition()}.
     * 
     * @param length
     *            A <code>long</code> which represents the size, in bytes, of the page blob.
     * @param premiumBlobTier
     *            A {@link PremiumPageBlobTier} object which represents the tier of the blob.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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 IllegalArgumentException
     *             If the length is not a multiple of 512.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void create(final long length, final PremiumPageBlobTier premiumBlobTier, final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        assertNoWriteOperationForSnapshot();

        if (length % Constants.PAGE_SIZE != 0) {
            throw new IllegalArgumentException(SR.INVALID_PAGE_BLOB_LENGTH);
        }

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

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

        ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.createImpl(length, premiumBlobTier, accessCondition, options), options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlob, Void> createImpl(final long length, final PremiumPageBlobTier premiumBlobTier,
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        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 {
                return BlobRequest.putBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
                        options, context, accessCondition, blob.properties, BlobType.PAGE_BLOB, length, premiumBlobTier);
            }

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

            @Override
            public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
                    throws Exception {
                StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, 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()));
                blob.getProperties().setLength(length);
                blob.getProperties().setPremiumPageBlobTier(premiumBlobTier);
                if (premiumBlobTier != null) {
                    blob.getProperties().setBlobTierInferred(false);
                }

                return null;
            }

        };

        return putRequest;
    }

    /**
     * Returns a collection of page ranges and their starting and ending byte offsets.
     * <p>
     * The start and end byte offsets for each page range are inclusive.
     * 
     * @return An <code>ArrayList</code> object which represents the set of page ranges and their starting and ending
     *         byte offsets.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ArrayList<PageRange> downloadPageRanges() throws StorageException {
        return this.downloadPageRanges(null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Returns a collection of page ranges and their starting and ending byte offsets using the specified request
     * options and operation context.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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.
     * 
     * @return An <code>ArrayList</code> object which represents the set of page ranges and their starting and ending
     *         byte offsets.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public ArrayList<PageRange> downloadPageRanges(final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {

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

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

        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.downloadPageRangesImpl(null /* offset */, null /* length */, accessCondition, options),
                options.getRetryPolicyFactory(), opContext);
    }

    /**
     * Returns a collection of page ranges and their starting and ending byte offsets.
     *
     * @param offset
     *            The starting offset of the data range over which to list page ranges, in bytes. Must be a multiple of
     *            512.
     * @param length
     *            The length of the data range over which to list page ranges, in bytes. Must be a multiple of 512.
     * @return A <code>List</code> object which represents the set of page ranges and their starting and ending
     *         byte offsets.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public List<PageRange> downloadPageRanges(final long offset, final Long length) throws StorageException {
        return this.downloadPageRanges(offset, length, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Returns a collection of page ranges and their starting and ending byte offsets using the specified request
     * options and operation context.
     * 
     * @param offset
     *            The starting offset of the data range over which to list page ranges, in bytes. Must be a multiple of
     *            512.
     * @param length
     *            The length of the data range over which to list page ranges, in bytes. Must be a multiple of 512.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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.
     * 
     * @return A <code>List</code> object which represents the set of page ranges and their starting and ending
     *         byte offsets.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public List<PageRange> downloadPageRanges(final long offset, final Long length,
            final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (offset < 0 || (length != null && length <= 0)) {
            throw new IndexOutOfBoundsException();
        }
        
        if (opContext == null) {
            opContext = new OperationContext();
        }

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

        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.downloadPageRangesImpl(offset, length, accessCondition, options), options.getRetryPolicyFactory(), opContext);
    }
    
    private StorageRequest<CloudBlobClient, CloudBlob, ArrayList<PageRange>> downloadPageRangesImpl(final Long offset,
            final Long length, final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlob, ArrayList<PageRange>> getRequest = new StorageRequest<CloudBlobClient, CloudBlob, ArrayList<PageRange>>(
                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.getPageRanges(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
                        options, context, accessCondition, blob.snapshotID, offset, length);
            }

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

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

                return null;
            }

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

                return PageRangeHandler.getPageRanges(this.getConnection().getInputStream());
            }

        };

        return getRequest;
    }
    
    /**
     * Gets the collection of page ranges that differ between a specified snapshot and this object.
     * 
     * @param previousSnapshot
     *            A string representing the snapshot to use as the starting point for the diff. If this
     *            CloudPageBlob represents a snapshot, the previousSnapshot parameter must be prior to the current
     *            snapshot.
     * @return A <code>List</code> object containing the set of differing page ranges.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public List<PageRangeDiff> downloadPageRangesDiff(final String previousSnapshot) throws StorageException {
        return this.downloadPageRangesDiff(previousSnapshot, null /* offset */, null/* length */,
                null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Gets the collection of page ranges that differ between a specified snapshot and this object.
     * 
     * @param previousSnapshot
     *            A string representing the snapshot timestamp to use as the starting point for the diff. If this
     *            CloudPageBlob represents a snapshot, the previousSnapshot parameter must be prior to the current
     *            snapshot.
     * @param offset
     *            The starting offset of the data range over which to list page ranges, in bytes. Must be a multiple of
     *            512.
     * @param length
     *            The length of the data range over which to list page ranges, in bytes. Must be a multiple of 512.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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.
     * 
     * @return A <code>List</code> object containing the set of differing page ranges.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public List<PageRangeDiff> downloadPageRangesDiff(final String previousSnapshot, final Long offset,
            final Long length, final AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {

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

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

        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.downloadPageRangesDiffImpl(previousSnapshot, offset, length, accessCondition, options),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudBlob, List<PageRangeDiff>> downloadPageRangesDiffImpl(
            final String previousSnapshot, final Long offset, final Long length, final AccessCondition accessCondition,
            final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudBlob, List<PageRangeDiff>> getRequest = new StorageRequest<CloudBlobClient, CloudBlob, List<PageRangeDiff>>(
                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.getPageRangesDiff(
                        blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context,
                        accessCondition, blob.snapshotID, previousSnapshot, offset, length);
            }

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

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

                return null;
            }

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

                return PageRangeDiffHandler.getPageRangesDiff(this.getConnection().getInputStream());
            }
        };

        return getRequest;
    }

    /**
     * Opens an output stream object to write data to the page blob. The page blob must already exist and any existing 
     * data may be overwritten.
     * 
     * @return A {@link BlobOutputStream} object used to write data to the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public BlobOutputStream openWriteExisting() throws StorageException {
        return this
                .openOutputStreamInternal(null /* length */, null /* premiumBlobTier */,null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Opens an output stream object to write data to the page blob, using the specified lease ID, request options and
     * operation context. The page blob must already exist and any existing data may be overwritten.
     * 
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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.
     * 
     * @return A {@link BlobOutputStream} object used to write data to the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public BlobOutputStream openWriteExisting(AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        return this.openOutputStreamInternal(null /* length */, null /* premiumBlobTier */, accessCondition, options, opContext);
    }

    /**
     * Opens an output stream object to write data to the page blob. The page blob does not need to yet exist and will
     * be created with the length specified. 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 #openWriteNew(long, AccessCondition, BlobRequestOptions, OperationContext)} overload with the appropriate 
     * {@link AccessCondition}.
     * 
     * @param length
     *            A <code>long</code> which represents the length, in bytes, of the stream to create. This value must be
     *            a multiple of 512.
     * 
     * @return A {@link BlobOutputStream} object used to write data to the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public BlobOutputStream openWriteNew(final long length) throws StorageException {
        return this
                .openOutputStreamInternal(length, null /* premiumBlobTier */, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Opens an output stream object to write data to the page blob, using the specified lease ID, request options and
     * operation context. The page blob does not need to yet exist and will be created with the length specified.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 length
     *            A <code>long</code> which represents the length, in bytes, of the stream to create. This value must be
     *            a multiple of 512.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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.
     *
     * @return A {@link BlobOutputStream} object used to write data to the blob.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public BlobOutputStream openWriteNew(final long length, AccessCondition accessCondition,
                                         BlobRequestOptions options, OperationContext opContext) throws StorageException {
        return openOutputStreamInternal(length, null /* premiumBlobTier */, accessCondition, options, opContext);
    }

    /**
     * Opens an output stream object to write data to the page blob, using the specified lease ID, request options and
     * operation context. The page blob does not need to yet exist and will be created with the length specified.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 length
     *            A <code>long</code> which represents the length, in bytes, of the stream to create. This value must be
     *            a multiple of 512.
     * @param premiumBlobTier
     *            A {@link PremiumPageBlobTier} object which represents the tier of the blob.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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.
     * 
     * @return A {@link BlobOutputStream} object used to write data to the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public BlobOutputStream openWriteNew(final long length, final PremiumPageBlobTier premiumBlobTier, AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException {
        return openOutputStreamInternal(length, premiumBlobTier, accessCondition, options, opContext);
    }

    /**
     * Opens an output stream object to write data to the page blob, using the specified lease ID, request options and
     * operation context. If the length is specified, a new page blob will be created with the length specified.
     * Otherwise, the page blob must already exist and a stream of its current length will be opened.
     * 
     * @param length
     *            A <code>long</code> which represents the length, in bytes, of the stream to create. This value must be
     *            a multiple of 512 or null if the
     *            page blob already exists.
     * @param premiumBlobTier
     *            A {@link PremiumPageBlobTier} object which represents the tier of the blob.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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.
     * 
     * @return A {@link BlobOutputStream} object used to write data to the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    private BlobOutputStream openOutputStreamInternal(Long length, PremiumPageBlobTier premiumBlobTier, AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

        assertNoWriteOperationForSnapshot();

        options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient, 
                false /* setStartTime */);

        if (options.getStoreBlobContentMD5()) {
            throw new IllegalArgumentException(SR.BLOB_MD5_NOT_SUPPORTED_FOR_PAGE_BLOBS);
        }

        if (length != null) {
            if (length % Constants.PAGE_SIZE != 0) {
                throw new IllegalArgumentException(SR.INVALID_PAGE_BLOB_LENGTH);
            }

            this.create(length, premiumBlobTier, accessCondition, options, opContext);
        }
        else {
            this.downloadAttributes(accessCondition, options, opContext);
            length = this.getProperties().getLength();
        }

        if (accessCondition != null) {
            accessCondition = AccessCondition.generateLeaseCondition(accessCondition.getLeaseID());
        }

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

    /**
     * Used for both uploadPages and clearPages.
     * 
     * @param pageRange
     *            A {@link PageRange} object that specifies the page range.
     * @param operationType
     *            A {@link PageOperationType} enumeration value that specifies the page operation type.
     * @param data
     *            A <code>byte</code> array which represents the data to write.
     * @param length
     *            A <code>long</code> which represents the number of bytes to write.
     * @param md5
     *            A <code>String</code> which represents the MD5 hash for the data.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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
    private void putPagesInternal(final PageRange pageRange, final PageOperationType operationType, final byte[] data,
            final long length, final String md5, final AccessCondition accessCondition,
            final BlobRequestOptions options, final OperationContext opContext) throws StorageException {
        ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                putPagesImpl(pageRange, operationType, data, length, md5, accessCondition, options, opContext),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest<CloudBlobClient, CloudPageBlob, Void> putPagesImpl(final PageRange pageRange,
            final PageOperationType operationType, final byte[] data, final long length, final String md5,
            final AccessCondition accessCondition, final BlobRequestOptions options, final OperationContext opContext) {
        final StorageRequest<CloudBlobClient, CloudPageBlob, Void> putRequest = new StorageRequest<CloudBlobClient, CloudPageBlob, Void>(
                options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudPageBlob blob, OperationContext context)
                    throws Exception {
                if (operationType == PageOperationType.UPDATE) {
                    this.setSendStream(new ByteArrayInputStream(data));
                    this.setLength(length);
                }

                return BlobRequest.putPage(blob.getTransformedAddress(opContext).getUri(this.getCurrentLocation()),
                        options, opContext, accessCondition, pageRange, operationType);
            }

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

            @Override
            public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
                    throws Exception {
                if (operationType == PageOperationType.UPDATE) {
                    StorageRequest.signBlobQueueAndFileRequest(connection, client, length, context);
                }
                else {
                    StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
                }
            }

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

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

        return putRequest;
    }
    
    protected void updateSequenceNumberFromResponse(HttpURLConnection request) {
        final String sequenceNumber = request.getHeaderField(Constants.HeaderConstants.BLOB_SEQUENCE_NUMBER);
        if (!Utility.isNullOrEmpty(sequenceNumber)) {
            this.getProperties().setPageBlobSequenceNumber(Long.parseLong(sequenceNumber));
        }
    }

    /**
     * Resizes the page blob to the specified size.
     * 
     * @param size
     *            A <code>long</code> which represents the size of the page blob, in bytes.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public void resize(long size) throws StorageException {
        this.resize(size, null /* accessCondition */, null /* options */, null /* operationContext */);
    }

    /**
     * Resizes the page blob to the specified size.
     * 
     * @param size
     *            A <code>long</code> which represents the size of the page blob, in bytes.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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.
     */
    public void resize(long size, AccessCondition accessCondition, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        assertNoWriteOperationForSnapshot();

        if (size % Constants.PAGE_SIZE != 0) {
            throw new IllegalArgumentException(SR.INVALID_PAGE_BLOB_LENGTH);
        }

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

        opContext.initialize();
        options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest<CloudBlobClient, CloudPageBlob, Void> resizeImpl(final long size,
            final AccessCondition accessCondition, final BlobRequestOptions options) {
        final StorageRequest<CloudBlobClient, CloudPageBlob, Void> putRequest = new StorageRequest<CloudBlobClient, CloudPageBlob, Void>(
                options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudPageBlob blob, OperationContext context)
                    throws Exception {
                return BlobRequest.resize(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
                        options, context, accessCondition, size);
            }

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

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

                blob.getProperties().setLength(size);
                blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
                blob.updateSequenceNumberFromResponse(this.getConnection());
                return null;
            }
        };

        return putRequest;
    }

    /**
     * Uploads a blob from data in a byte array. If the blob already exists on the service, it will be overwritten.
     *
     * @param buffer
     *            A <code>byte</code> array which represents the data to write to the blob.
     * @param offset
     *            A <code>int</code> which represents the offset of the byte array from which to start the data upload.
     * @param length
     *            An <code>int</code> which represents the number of bytes to upload from the input buffer.
     * @param premiumBlobTier
     *            A {@link PremiumPageBlobTier} object which represents the tier 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.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws IOException
     */
    public void uploadFromByteArray(final byte[] buffer, final int offset, final int length, final PremiumPageBlobTier premiumBlobTier,
                                    final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException, IOException {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer, offset, length);
        this.upload(inputStream, length, premiumBlobTier, accessCondition, options, opContext);
        inputStream.close();
    }

    /**
     * Uploads a blob from a file. If the blob already exists on the service, it will be overwritten.
     *
     * @param path
     *            A <code>String</code> which represents the path to the file to be uploaded.
     * @param premiumBlobTier
     *            A {@link PremiumPageBlobTier} object which represents the tier 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.
     *
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws IOException
     */
    public void uploadFromFile(final String path, final PremiumPageBlobTier premiumBlobTier, final AccessCondition accessCondition, BlobRequestOptions options,
                               OperationContext opContext) throws StorageException, IOException {
        File file = new File(path);
        long fileLength = file.length();
        InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
        this.upload(inputStream, fileLength, premiumBlobTier, accessCondition, options, opContext);
        inputStream.close();
    }

    /**
     * Uploads the source stream data to the page blob. If the blob already exists on the service, it will be 
     * overwritten.
     * 
     * @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, must be non zero and a
     *            multiple of 512.
     * 
     * @throws IOException
     *             If an I/O exception 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 /* premiumBlobTier */, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Uploads the source stream data to the page 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 to read from.
     * @param length
     *            A <code>long</code> which represents the length, in bytes, of the stream data. This must be great than
     *            zero and a multiple of 512.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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 IOException
     *             If an I/O exception 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 {
        this.upload(sourceStream, length, null /* premiumBlobTier*/, accessCondition, options, opContext);
    }

    /**
     * Uploads the source stream data to the page 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 to read from.
     * @param length
     *            A <code>long</code> which represents the length, in bytes, of the stream data. This must be great than
     *            zero and a multiple of 512.
     * @param premiumBlobTier
     *            A {@link PremiumPageBlobTier} object which represents the tier of the blob.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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 IOException
     *             If an I/O exception occurred.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void upload(final InputStream sourceStream, final long length, final PremiumPageBlobTier premiumBlobTier, final AccessCondition accessCondition,
            BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException {
        assertNoWriteOperationForSnapshot();

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

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

        if (length <= 0 || length % Constants.PAGE_SIZE != 0) {
            throw new IllegalArgumentException(SR.INVALID_PAGE_BLOB_LENGTH);
        }

        if (options.getStoreBlobContentMD5()) {
            throw new IllegalArgumentException(SR.BLOB_MD5_NOT_SUPPORTED_FOR_PAGE_BLOBS);
        }

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

        final BlobOutputStream streamRef = this.openWriteNew(length, premiumBlobTier, accessCondition, options, opContext);
        try {
            streamRef.write(sourceStream, length);
        }
        finally {
            streamRef.close();
        }
    }

    /**
     * Uploads a range of contiguous pages, up to 4 MB in size, at the specified offset in the page blob.
     * 
     * @param sourceStream
     *            An {@link InputStream} object which represents the input stream to write to the page blob.
     * @param offset
     *            A <code>long</code> which represents the offset, in number of bytes, at which to begin writing the
     *            data. This value must be a multiple of 512.
     * @param length
     *            A <code>long</code> which represents the length, in bytes, of the data to write. This value must be a
     *            multiple of 512.
     * 
     * @throws IllegalArgumentException
     *             If the offset or length are not multiples of 512, or if the length is greater than 4 MB.
     * @throws IOException
     *             If an I/O exception occurred.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadPages(final InputStream sourceStream, final long offset, final long length)
            throws StorageException, IOException {
        this.uploadPages(sourceStream, offset, length, null /* accessCondition */, null /* options */, null /* opContext */);
    }

    /**
     * Uploads a range of contiguous pages, up to 4 MB in size, at the specified offset in the page blob, using the
     * specified lease ID, request options, and operation context.
     * 
     * @param sourceStream
     *            An {@link InputStream} object which represents the input stream to write to the page blob.
     * @param offset
     *            A <code>long</code> which represents the offset, in number of bytes, at which to begin writing the
     *            data. This value must be a multiple of
     *            512.
     * @param length
     *            A <code>long</code> which represents the length, in bytes, of the data to write. This value must be a
     *            multiple of 512.
     * @param accessCondition
     *            An {@link AccessCondition} object which 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 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 IllegalArgumentException
     *             If the offset or length are not multiples of 512, or if the length is greater than 4 MB.
     * @throws IOException
     *             If an I/O exception occurred.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadPages(final InputStream sourceStream, final long offset, final long length,
            final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException, IOException {

        if (offset % Constants.PAGE_SIZE != 0) {
            throw new IllegalArgumentException(SR.INVALID_PAGE_START_OFFSET);
        }

        if (length == 0 || length % Constants.PAGE_SIZE != 0) {
            throw new IllegalArgumentException(SR.INVALID_PAGE_BLOB_LENGTH);
        }

        if (length > 4 * Constants.MB) {
            throw new IllegalArgumentException(SR.INVALID_MAX_WRITE_SIZE);
        }

        assertNoWriteOperationForSnapshot();

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

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

        final PageRange pageRange = new PageRange(offset, offset + length - 1);
        final byte[] data = new byte[(int) length];
        String md5 = null;

        int count = 0;
        int total = 0;
        while (total < length) {
            count = sourceStream.read(data, total, (int) Math.min(length - total, Integer.MAX_VALUE));
            total += count;
        }

        if (options.getUseTransactionalContentMD5()) {
            try {
                final MessageDigest digest = MessageDigest.getInstance("MD5");
                digest.update(data, 0, data.length);
                md5 = Base64.encode(digest.digest());
            }
            catch (final NoSuchAlgorithmException e) {
                // This wont happen, throw fatal.
                throw Utility.generateNewUnexpectedStorageException(e);
            }
        }

        this.putPagesInternal(pageRange, PageOperationType.UPDATE, data, length, md5, accessCondition, options,
                opContext);
    }

    /**
     * Sets the number of bytes to buffer when writing to a {@link BlobOutputStream}.
     * 
     * @param streamWriteSizeInBytes
     *            An <code>int</code> which represents the maximum number of bytes to buffer when writing to a page blob
     *            stream. This value must be a
     *            multiple of 512 and
     *            less than or equal to 4 MB.
     * 
     * @throws IllegalArgumentException
     *             If <code>streamWriteSizeInBytes</code> is less than 512, greater than 4 MB, or not a multiple or 512.
     */
    @Override
    public void setStreamWriteSizeInBytes(final int streamWriteSizeInBytes) {
        if (streamWriteSizeInBytes > Constants.MAX_BLOCK_SIZE || streamWriteSizeInBytes < Constants.PAGE_SIZE
                || streamWriteSizeInBytes % Constants.PAGE_SIZE != 0) {
            throw new IllegalArgumentException("StreamWriteSizeInBytes");
        }

        this.streamWriteSizeInBytes = streamWriteSizeInBytes;
    }
    
    /**
     * Sets the blob tier on a page blob on a premium storage account.
     * @param premiumBlobTier
     *            A {@link PremiumPageBlobTier} object which represents the tier of the blob.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public void uploadPremiumPageBlobTier(final PremiumPageBlobTier premiumBlobTier) throws StorageException {
        this.uploadPremiumPageBlobTier(premiumBlobTier, null /* options */, null /* opContext */);
    }

    /**
     * Sets the tier on a page blob on a premium storage account.
     * @param premiumBlobTier
     *            A {@link PremiumPageBlobTier} 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 uploadPremiumPageBlobTier(final PremiumPageBlobTier premiumBlobTier, BlobRequestOptions options,
            OperationContext opContext) throws StorageException {
        assertNoWriteOperationForSnapshot();
        Utility.assertNotNull("premiumBlobTier", premiumBlobTier);

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

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

        ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.uploadBlobTierImpl(premiumBlobTier.toString(), options), options.getRetryPolicyFactory(), opContext);
        this.properties.setPremiumPageBlobTier(premiumBlobTier);
        this.properties.setBlobTierInferred(false);
    }
} 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.";
} public final class Utility {

    /**
     * Stores a reference to the GMT time zone.
     */
    public static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT");

    /**
     * Stores a reference to the UTC time zone.
     */
    public static final TimeZone UTC_ZONE = TimeZone.getTimeZone("UTC");

    /**
     * Stores a reference to the US locale.
     */
    public static final Locale LOCALE_US = Locale.US;

    /**
     * Stores a reference to the RFC1123 date/time pattern.
     */
    private static final String RFC1123_GMT_PATTERN = "EEE, dd MMM yyyy HH:mm:ss 'GMT'";

    /**
     * Stores a reference to the ISO8601 date/time pattern.
     */
    private static final String ISO8601_PATTERN_NO_SECONDS = "yyyy-MM-dd'T'HH:mm'Z'";

    /**
     * Stores a reference to the ISO8601 date/time pattern.
     */
    private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";

    /**
     * Stores a reference to the Java version of ISO8601_LONG date/time pattern.  The full version cannot be used
     * because Java Dates have millisecond precision.
     */
    private static final String JAVA_ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
    
    /**
     * List of ports used for path style addressing.
     */
    private static final List<Integer> pathStylePorts = Arrays.asList(10000, 10001, 10002, 10003, 10004, 10100, 10101,
            10102, 10103, 10104, 11000, 11001, 11002, 11003, 11004, 11100, 11101, 11102, 11103, 11104);

    /**
     * A factory to create SAXParser instances.
     */
    private static final SAXParserFactory factory = SAXParserFactory.newInstance();

    /**
     * Stores a reference to the date/time pattern with the greatest precision Java.util.Date is capable of expressing.
     */
    private static final String MAX_PRECISION_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS";
    
    /**
     * The length of a datestring that matches the MAX_PRECISION_PATTERN.
     */
    private static final int MAX_PRECISION_DATESTRING_LENGTH = MAX_PRECISION_PATTERN.replaceAll("'", "").length();
    
    /**
     * 
     * Determines the size of an input stream, and optionally calculates the MD5 hash for the stream.
     * 
     * @param sourceStream
     *            A <code>InputStream</code> object that represents the stream to measure.
     * @param writeLength
     *            The number of bytes to read from the stream.
     * @param abandonLength
     *            The number of bytes to read before the analysis is abandoned. Set this value to <code>-1</code> to
     *            force the entire stream to be read. This parameter is provided to support upload thresholds.
     * @param rewindSourceStream
     *            <code>true</code> if the stream should be rewound after it is read; otherwise, <code>false</code>.
     * @param calculateMD5
     *            <code>true</code> if an MD5 hash will be calculated; otherwise, <code>false</code>.
     * 
     * @return A {@link StreamMd5AndLength} object that contains the stream length, and optionally the MD5 hash.
     * 
     * @throws IOException
     *             If an I/O error occurs.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public static StreamMd5AndLength analyzeStream(final InputStream sourceStream, long writeLength,
            long abandonLength, final boolean rewindSourceStream, final boolean calculateMD5) throws IOException,
            StorageException {
        if (abandonLength < 0) {
            abandonLength = Long.MAX_VALUE;
        }

        if (rewindSourceStream) {
            if (!sourceStream.markSupported()) {
                throw new IllegalArgumentException(SR.INPUT_STREAM_SHOULD_BE_MARKABLE);
            }

            sourceStream.mark(Constants.MAX_MARK_LENGTH);
        }

        MessageDigest digest = null;
        if (calculateMD5) {
            try {
                digest = MessageDigest.getInstance("MD5");
            }
            catch (final NoSuchAlgorithmException e) {
                // This wont happen, throw fatal.
                throw Utility.generateNewUnexpectedStorageException(e);
            }
        }

        if (writeLength < 0) {
            writeLength = Long.MAX_VALUE;
        }

        final StreamMd5AndLength retVal = new StreamMd5AndLength();
        int count = -1;
        final byte[] retrievedBuff = new byte[Constants.BUFFER_COPY_LENGTH];

        int nextCopy = (int) Math.min(retrievedBuff.length, writeLength - retVal.getLength());
        count = sourceStream.read(retrievedBuff, 0, nextCopy);

        while (nextCopy > 0 && count != -1) {
            if (calculateMD5) {
                digest.update(retrievedBuff, 0, count);
            }
            retVal.setLength(retVal.getLength() + count);

            if (retVal.getLength() > abandonLength) {
                // Abandon operation
                retVal.setLength(-1);
                retVal.setMd5(null);
                break;
            }

            nextCopy = (int) Math.min(retrievedBuff.length, writeLength - retVal.getLength());
            count = sourceStream.read(retrievedBuff, 0, nextCopy);
        }

        if (retVal.getLength() != -1 && calculateMD5) {
            retVal.setMd5(Base64.encode(digest.digest()));
        }

        if (retVal.getLength() != -1 && writeLength > 0) {
            retVal.setLength(Math.min(retVal.getLength(), writeLength));
        }

        if (rewindSourceStream) {
            sourceStream.reset();
            sourceStream.mark(Constants.MAX_MARK_LENGTH);
        }

        return retVal;
    }

    /**
     * Asserts a continuation token is of the specified type.
     * 
     * @param continuationToken
     *            A {@link ResultContinuation} object that represents the continuation token whose type is being
     *            examined.
     * @param continuationType
     *            A {@link ResultContinuationType} value that represents the continuation token type being asserted with
     *            the specified continuation token.
     */
    public static void assertContinuationType(final ResultContinuation continuationToken,
            final ResultContinuationType continuationType) {
        if (continuationToken != null) {
            if (!(continuationToken.getContinuationType() == ResultContinuationType.NONE || continuationToken
                    .getContinuationType() == continuationType)) {
                final String errorMessage = String.format(Utility.LOCALE_US, SR.UNEXPECTED_CONTINUATION_TYPE,
                        continuationToken.getContinuationType(), continuationType);
                throw new IllegalArgumentException(errorMessage);
            }
        }
    }

    /**
     * Asserts that a value is not <code>null</code>.
     * 
     * @param param
     *            A <code>String</code> that represents the name of the parameter, which becomes the exception message
     *            text if the <code>value</code> parameter is <code>null</code>.
     * @param value
     *            An <code>Object</code> object that represents the value of the specified parameter. This is the value
     *            being asserted as not <code>null</code>.
     */
    public static void assertNotNull(final String param, final Object value) {
        if (value == null) {
            throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.ARGUMENT_NULL_OR_EMPTY, param));
        }
    }

    /**
     * Asserts that the specified string is not <code>null</code> or empty.
     * 
     * @param param
     *            A <code>String</code> that represents the name of the parameter, which becomes the exception message
     *            text if the <code>value</code> parameter is <code>null</code> or an empty string.
     * @param value
     *            A <code>String</code> that represents the value of the specified parameter. This is the value being
     *            asserted as not <code>null</code> and not an empty string.
     */
    public static void assertNotNullOrEmpty(final String param, final String value) {
        assertNotNull(param, value);

        if (Utility.isNullOrEmpty(value)) {
            throw new IllegalArgumentException(String.format(Utility.LOCALE_US, SR.ARGUMENT_NULL_OR_EMPTY, param));
        }
    }

    /**
     * Asserts that the specified integer is in the valid range.
     * 
     * @param param
     *            A <code>String</code> that represents the name of the parameter, which becomes the exception message
     *            text if the <code>value</code> parameter is out of bounds.
     * @param value
     *            The value of the specified parameter.
     * @param min
     *            The minimum value for the specified parameter.
     * @param max
     *            The maximum value for the specified parameter.
     */
    public static void assertInBounds(final String param, final long value, final long min, final long max) {
        if (value < min || value > max) {
            throw new IllegalArgumentException(String.format(SR.PARAMETER_NOT_IN_RANGE, param, min, max));
        }
    }

    /**
     * Asserts that the specified value is greater than or equal to the min value.
     * 
     * @param param
     *            A <code>String</code> that represents the name of the parameter, which becomes the exception message
     *            text if the <code>value</code> parameter is out of bounds.
     * @param value
     *            The value of the specified parameter.
     * @param min
     *            The minimum value for the specified parameter.
     */
    public static void assertGreaterThanOrEqual(final String param, final long value, final long min) {
        if (value < min) {
            throw new IllegalArgumentException(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, param, min));
        }
    }

    /**
     * Returns a value representing whether the maximum execution time would be surpassed.
     * 
     * @param operationExpiryTimeInMs
     *            the time the request expires
     * @return <code>true</code> if the maximum execution time would be surpassed; otherwise, <code>false</code>.
     */
    public static boolean validateMaxExecutionTimeout(Long operationExpiryTimeInMs) {
        return validateMaxExecutionTimeout(operationExpiryTimeInMs, 0);
    }

    /**
     * Returns a value representing whether the maximum execution time would be surpassed.
     * 
     * @param operationExpiryTimeInMs
     *            the time the request expires
     * @param additionalInterval
     *            any additional time required from now
     * @return <code>true</code> if the maximum execution time would be surpassed; otherwise, <code>false</code>.
     */
    public static boolean validateMaxExecutionTimeout(Long operationExpiryTimeInMs, long additionalInterval) {
        if (operationExpiryTimeInMs != null) {
            long currentTime = new Date().getTime();
            return operationExpiryTimeInMs < currentTime + additionalInterval;
        }
        return false;
    }

    /**
     * Returns a value representing the remaining time before the operation expires.
     * 
     * @param operationExpiryTimeInMs
     *            the time the request expires
     * @param timeoutIntervalInMs
     *            the server side timeout interval
     * @return the remaining time before the operation expires
     * @throws StorageException
     *             wraps a TimeoutException if there is no more time remaining
     */
    public static int getRemainingTimeout(Long operationExpiryTimeInMs, Integer timeoutIntervalInMs) throws StorageException {
        if (operationExpiryTimeInMs != null) {
            long remainingTime = operationExpiryTimeInMs - new Date().getTime();
            if (remainingTime > Integer.MAX_VALUE) {
                return Integer.MAX_VALUE;
            }
            else if (remainingTime > 0) {
                return (int) remainingTime;
            }
            else {
                TimeoutException timeoutException = new TimeoutException(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION);
                StorageException translatedException = new StorageException(
                        StorageErrorCodeStrings.OPERATION_TIMED_OUT, SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION,
                        Constants.HeaderConstants.HTTP_UNUSED_306, null, timeoutException);
                throw translatedException;
            }
        }
        else if (timeoutIntervalInMs != null) {
            return timeoutIntervalInMs + Constants.DEFAULT_READ_TIMEOUT;
        }
        else {
            return Constants.DEFAULT_READ_TIMEOUT;
        }
    }

    /**
     * Returns a value that indicates whether a specified URI is a path-style URI.
     * 
     * @param baseURI
     *            A <code>java.net.URI</code> value that represents the URI being checked.
     * @return <code>true</code> if the specified URI is path-style; otherwise, <code>false</code>.
     */
    public static boolean determinePathStyleFromUri(final URI baseURI) {
        String path = baseURI.getPath();
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }

        // if the path is null or empty, this is not path-style
        if (Utility.isNullOrEmpty(path)) {
            return false;
        }

        // if this contains a port or has a host which is not DNS, this is path-style
        return pathStylePorts.contains(baseURI.getPort()) || !isHostDnsName(baseURI);
    }

    /**
     * Returns a boolean indicating whether the host of the specified URI is DNS.
     * 
     * @param uri
     *            The URI whose host to evaluate.
     * @return <code>true</code> if the host is DNS; otherwise, <code>false</code>.
     */
    private static boolean isHostDnsName(URI uri) {
        String host = uri.getHost();
        for (int i = 0; i < host.length(); i++) {
            char hostChar = host.charAt(i);
            if (!Character.isDigit(hostChar) && !(hostChar == '.')) {
                return true;
            }
        }
        return false;
    }

    /**
     * Reads character data for the Etag element from an XML stream reader.
     * 
     * @param xmlr
     *            An <code>XMLStreamReader</code> object that represents the source XML stream reader.
     * 
     * @return A <code>String</code> that represents the character data for the Etag element.
     */
    public static String formatETag(final String etag) {
        if (etag.startsWith("\"") && etag.endsWith("\"")) {
            return etag;
        }
        else {
            return String.format("\"%s\"", etag);
        }
    }

    /**
     * Returns an unexpected storage exception.
     * 
     * @param cause
     *            An <code>Exception</code> object that represents the initial exception that caused the unexpected
     *            error.
     * 
     * @return A {@link StorageException} object that represents the unexpected storage exception being thrown.
     */
    public static StorageException generateNewUnexpectedStorageException(final Exception cause) {
        final StorageException exceptionRef = new StorageException(StorageErrorCode.NONE.toString(),
                "Unexpected internal storage client error.", 306, // unused
                null, null);
        exceptionRef.initCause(cause);
        return exceptionRef;
    }

    /**
     * Returns a byte array that represents the data of a <code>long</code> value.
     * 
     * @param value
     *            The value from which the byte array will be returned.
     * 
     * @return A byte array that represents the data of the specified <code>long</code> value.
     */
    public static byte[] getBytesFromLong(final long value) {
        final byte[] tempArray = new byte[8];

        for (int m = 0; m < 8; m++) {
            tempArray[7 - m] = (byte) ((value >> (8 * m)) & 0xFF);
        }

        return tempArray;
    }

    /**
     * Returns the current GMT date/time String using the RFC1123 pattern.
     * 
     * @return A <code>String</code> that represents the current GMT date/time using the RFC1123 pattern.
     */
    public static String getGMTTime() {
        return getGMTTime(new Date());
    }

    /**
     * Returns the GTM date/time String for the specified value using the RFC1123 pattern.
     *
     * @param date
     *            A <code>Date</code> object that represents the date to convert to GMT date/time in the RFC1123
     *            pattern.
     *
     * @return A <code>String</code> that represents the GMT date/time for the specified value using the RFC1123
     *         pattern.
     */
    public static String getGMTTime(final Date date) {
        final DateFormat formatter = new SimpleDateFormat(RFC1123_GMT_PATTERN, LOCALE_US);
        formatter.setTimeZone(GMT_ZONE);
        return formatter.format(date);
    }

    /**
     * Returns the UTC date/time String for the specified value using Java's version of the ISO8601 pattern,
     * which is limited to millisecond precision.
     * 
     * @param date
     *            A <code>Date</code> object that represents the date to convert to UTC date/time in Java's version
     *            of the ISO8601 pattern.
     * 
     * @return A <code>String</code> that represents the UTC date/time for the specified value using Java's version
     *            of the ISO8601 pattern.
     */
    public static String getJavaISO8601Time(Date date) {
        final DateFormat formatter = new SimpleDateFormat(JAVA_ISO8601_PATTERN, LOCALE_US);
        formatter.setTimeZone(UTC_ZONE);
        return formatter.format(date);
    }
    
    /**
     * Returns a namespace aware <code>SAXParser</code>.
     * 
     * @return A <code>SAXParser</code> instance which is namespace aware
     * 
     * @throws ParserConfigurationException
     * @throws SAXException
     */
    public static SAXParser getSAXParser() throws ParserConfigurationException, SAXException {
        factory.setNamespaceAware(true);
        return factory.newSAXParser();
    }
    
    /**
     * Returns the standard header value from the specified connection request, or an empty string if no header value
     * has been specified for the request.
     * 
     * @param conn
     *            An <code>HttpURLConnection</code> object that represents the request.
     * @param headerName
     *            A <code>String</code> that represents the name of the header being requested.
     * 
     * @return A <code>String</code> that represents the header value, or <code>null</code> if there is no corresponding
     *         header value for <code>headerName</code>.
     */
    public static String getStandardHeaderValue(final HttpURLConnection conn, final String headerName) {
        final String headerValue = conn.getRequestProperty(headerName);

        // Coalesce null value
        return headerValue == null ? Constants.EMPTY_STRING : headerValue;
    }

    /**
     * Returns the UTC date/time for the specified value using the ISO8601 pattern.
     * 
     * @param value
     *            A <code>Date</code> object that represents the date to convert to UTC date/time in the ISO8601
     *            pattern. If this value is <code>null</code>, this method returns an empty string.
     * 
     * @return A <code>String</code> that represents the UTC date/time for the specified value using the ISO8601
     *         pattern, or an empty string if <code>value</code> is <code>null</code>.
     */
    public static String getUTCTimeOrEmpty(final Date value) {
        if (value == null) {
            return Constants.EMPTY_STRING;
        }

        final DateFormat iso8601Format = new SimpleDateFormat(ISO8601_PATTERN, LOCALE_US);
        iso8601Format.setTimeZone(UTC_ZONE);

        return iso8601Format.format(value);
    }

    /**
     * Returns a <code>XmlSerializer</code> which outputs to the specified <code>StringWriter</code>
     * 
     * @param outWriter
     *            the <code>StringWriter</code> to output to
     * @return A <code>XmlSerializer</code> instance
     * 
     * @throws IOException
     *             if there is an error setting the output.
     * @throws IllegalStateException
     *             if there is an error setting the output.
     * @throws IllegalArgumentException
     *             if there is an error setting the output.
     */
    public static XmlSerializer getXmlSerializer(StringWriter outWriter) throws IllegalArgumentException,
            IllegalStateException, IOException {
        XmlSerializer serializer = Xml.newSerializer();
        serializer.setOutput(outWriter);

        return serializer;
    }

    /**
     * Creates an instance of the <code>IOException</code> class using the specified exception.
     * 
     * @param ex
     *            An <code>Exception</code> object that represents the exception used to create the IO exception.
     * 
     * @return A <code>java.io.IOException</code> object that represents the created IO exception.
     */
    public static IOException initIOException(final Exception ex) {
        String message = null;
        if (ex != null && ex.getMessage() != null) {
            message = ex.getMessage() + " Please see the cause for further information.";
        }
        else {
            message = "Please see the cause for further information.";
        }

        final IOException retEx = new IOException(message, ex);
        return retEx;
    }

    /**
     * Returns a value that indicates whether the specified string is <code>null</code> or empty.
     * 
     * @param value
     *            A <code>String</code> being examined for <code>null</code> or empty.
     * 
     * @return <code>true</code> if the specified value is <code>null</code> or empty; otherwise, <code>false</code>
     */
    public static boolean isNullOrEmpty(final String value) {
        return value == null || value.length() == 0;
    }

    /**
     * Returns a value that indicates whether the specified string is <code>null</code>, empty, or whitespace.
     * 
     * @param value
     *            A <code>String</code> being examined for <code>null</code>, empty, or whitespace.
     * 
     * @return <code>true</code> if the specified value is <code>null</code>, empty, or whitespace; otherwise,
     *         <code>false</code>
     */
    public static boolean isNullOrEmptyOrWhitespace(final String value) {
        return value == null || value.trim().length() == 0;
    }

    /**
     * Parses a connection string and returns its values as a hash map of key/value pairs.
     * 
     * @param parseString
     *            A <code>String</code> that represents the connection string to parse.
     * 
     * @return A <code>java.util.HashMap</code> object that represents the hash map of the key / value pairs parsed from
     *         the connection string.
     */
    public static HashMap<String, String> parseAccountString(final String parseString) {

        // 1. split name value pairs by splitting on the ';' character
        final String[] valuePairs = parseString.split(";");
        final HashMap<String, String> retVals = new HashMap<String, String>();

        // 2. for each field value pair parse into appropriate map entries
        for (int m = 0; m < valuePairs.length; m++) {
            if (valuePairs[m].length() == 0) {
                continue;
            }
            final int equalDex = valuePairs[m].indexOf("=");
            if (equalDex < 1) {
                throw new IllegalArgumentException(SR.INVALID_CONNECTION_STRING);
            }

            final String key = valuePairs[m].substring(0, equalDex);
            final String value = valuePairs[m].substring(equalDex + 1);

            // 2.1 add to map
            retVals.put(key, value);
        }

        return retVals;
    }

    /**
     * Returns a GMT date in the specified format
     * 
     * @param value
     *            the string to parse
     * @return the GMT date, as a <code>Date</code>
     * @throws ParseException
     *             If the specified string is invalid
     */
    public static Date parseDateFromString(final String value, final String pattern, final TimeZone timeZone)
            throws ParseException {
        final DateFormat rfc1123Format = new SimpleDateFormat(pattern, Utility.LOCALE_US);
        rfc1123Format.setTimeZone(timeZone);
        return rfc1123Format.parse(value);
   }

    /**
     * Returns a GMT date for the specified string in the RFC1123 pattern.
     * 
     * @param value
     *            A <code>String</code> that represents the string to parse.
     * 
     * @return A <code>Date</code> object that represents the GMT date in the RFC1123 pattern.
     * 
     * @throws ParseException
     *             If the specified string is invalid.
     */
    public static Date parseRFC1123DateFromStringInGMT(final String value) throws ParseException {
        final DateFormat format = new SimpleDateFormat(RFC1123_GMT_PATTERN, Utility.LOCALE_US);
        format.setTimeZone(GMT_ZONE);
        return format.parse(value);
    }

    /**
     * Performs safe decoding of the specified string, taking care to preserve each <code>+</code> character, rather
     * than replacing it with a space character.
     * 
     * @param stringToDecode
     *            A <code>String</code> that represents the string to decode.
     * 
     * @return A <code>String</code> that represents the decoded string.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public static String safeDecode(final String stringToDecode) throws StorageException {
        if (stringToDecode == null) {
            return null;
        }

        if (stringToDecode.length() == 0) {
            return Constants.EMPTY_STRING;
        }

        try {
            if (stringToDecode.contains("+")) {
                final StringBuilder outBuilder = new StringBuilder();

                int startDex = 0;
                for (int m = 0; m < stringToDecode.length(); m++) {
                    if (stringToDecode.charAt(m) == '+') {
                        if (m > startDex) {
                            outBuilder.append(URLDecoder.decode(stringToDecode.substring(startDex, m),
                                    Constants.UTF8_CHARSET));
                        }

                        outBuilder.append("+");
                        startDex = m + 1;
                    }
                }

                if (startDex != stringToDecode.length()) {
                    outBuilder.append(URLDecoder.decode(stringToDecode.substring(startDex, stringToDecode.length()),
                            Constants.UTF8_CHARSET));
                }

                return outBuilder.toString();
            }
            else {
                return URLDecoder.decode(stringToDecode, Constants.UTF8_CHARSET);
            }
        }
        catch (final UnsupportedEncodingException e) {
            throw Utility.generateNewUnexpectedStorageException(e);
        }
    }

    /**
     * Performs safe encoding of the specified string, taking care to insert <code>%20</code> for each space character,
     * instead of inserting the <code>+</code> character.
     * 
     * @param stringToEncode
     *            A <code>String</code> that represents the string to encode.
     * 
     * @return A <code>String</code> that represents the encoded string.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public static String safeEncode(final String stringToEncode) throws StorageException {
        if (stringToEncode == null) {
            return null;
        }
        if (stringToEncode.length() == 0) {
            return Constants.EMPTY_STRING;
        }

        try {
            final String tString = URLEncoder.encode(stringToEncode, Constants.UTF8_CHARSET);

            if (stringToEncode.contains(" ")) {
                final StringBuilder outBuilder = new StringBuilder();

                int startDex = 0;
                for (int m = 0; m < stringToEncode.length(); m++) {
                    if (stringToEncode.charAt(m) == ' ') {
                        if (m > startDex) {
                            outBuilder.append(URLEncoder.encode(stringToEncode.substring(startDex, m),
                                    Constants.UTF8_CHARSET));
                        }

                        outBuilder.append("%20");
                        startDex = m + 1;
                    }
                }

                if (startDex != stringToEncode.length()) {
                    outBuilder.append(URLEncoder.encode(stringToEncode.substring(startDex, stringToEncode.length()),
                            Constants.UTF8_CHARSET));
                }

                return outBuilder.toString();
            }
            else {
                return tString;
            }

        }
        catch (final UnsupportedEncodingException e) {
            throw Utility.generateNewUnexpectedStorageException(e);
        }
    }

    /**
     * Determines the relative difference between the two specified URIs.
     * 
     * @param baseURI
     *            A <code>java.net.URI</code> object that represents the base URI for which <code>toUri</code> will be
     *            made relative.
     * @param toUri
     *            A <code>java.net.URI</code> object that represents the URI to make relative to <code>baseURI</code>.
     * 
     * @return A <code>String</code> that either represents the relative URI of <code>toUri</code> to
     *         <code>baseURI</code>, or the URI of <code>toUri</code> itself, depending on whether the hostname and
     *         scheme are identical for <code>toUri</code> and <code>baseURI</code>. If the hostname and scheme of
     *         <code>baseURI</code> and <code>toUri</code> are identical, this method returns an unencoded relative URI
     *         such that if appended to <code>baseURI</code>, it will yield <code>toUri</code>. If the hostname or
     *         scheme of <code>baseURI</code> and <code>toUri</code> are not identical, this method returns an unencoded
     *         full URI specified by <code>toUri</code>.
     * 
     * @throws URISyntaxException
     *             If <code>baseURI</code> or <code>toUri</code> is invalid.
     */
    public static String safeRelativize(final URI baseURI, final URI toUri) throws URISyntaxException {
        // For compatibility followed
        // http://msdn.microsoft.com/en-us/library/system.uri.makerelativeuri.aspx

        // if host and scheme are not identical return from uri
        if (!baseURI.getHost().equals(toUri.getHost()) || !baseURI.getScheme().equals(toUri.getScheme())) {
            return toUri.toString();
        }

        final String basePath = baseURI.getPath();
        String toPath = toUri.getPath();

        int truncatePtr = 1;

        // Seek to first Difference
        // int maxLength = Math.min(basePath.length(), toPath.length());
        int m = 0;
        int ellipsesCount = 0;
        for (; m < basePath.length(); m++) {
            if (m >= toPath.length()) {
                if (basePath.charAt(m) == '/') {
                    ellipsesCount++;
                }
            }
            else {
                if (basePath.charAt(m) != toPath.charAt(m)) {
                    break;
                }
                else if (basePath.charAt(m) == '/') {
                    truncatePtr = m + 1;
                }
            }
        }

        // ../containername and ../containername/{path} should increment the truncatePtr
        // otherwise toPath will incorrectly begin with /containername
        if (m < toPath.length() && toPath.charAt(m) == '/') {
            // this is to handle the empty directory case with the '/' delimiter
            // for example, ../containername/ and ../containername// should not increment the truncatePtr
            if (!(toPath.charAt(m - 1) == '/' && basePath.charAt(m - 1) == '/')) {
                truncatePtr = m + 1;
            }
        }

        if (m == toPath.length()) {
            // No path difference, return query + fragment
            return new URI(null, null, null, toUri.getQuery(), toUri.getFragment()).toString();
        }
        else {
            toPath = toPath.substring(truncatePtr);
            final StringBuilder sb = new StringBuilder();
            while (ellipsesCount > 0) {
                sb.append("../");
                ellipsesCount--;
            }

            if (!Utility.isNullOrEmpty(toPath)) {
                sb.append(toPath);
            }

            if (!Utility.isNullOrEmpty(toUri.getQuery())) {
                sb.append("?");
                sb.append(toUri.getQuery());
            }
            if (!Utility.isNullOrEmpty(toUri.getFragment())) {
                sb.append("#");
                sb.append(toUri.getRawFragment());
            }

            return sb.toString();
        }
    }

    /**
     * Serializes the parsed StorageException. If an exception is encountered, returns empty string.
     * 
     * @param ex
     *            The StorageException to serialize.
     * @param opContext 
     *            The operation context which provides the logger.
     */
    public static void logHttpError(StorageException ex, OperationContext opContext) {
        if (Logger.shouldLog(opContext, Log.DEBUG)) {
            try {
                StringBuilder bld = new StringBuilder();
                bld.append("Error response received. ");
    
                bld.append("HttpStatusCode= ");
                bld.append(ex.getHttpStatusCode());
                
                bld.append(", HttpStatusMessage= ");
                bld.append(ex.getMessage());
    
                bld.append(", ErrorCode= ");
                bld.append(ex.getErrorCode());
    
                StorageExtendedErrorInformation extendedError = ex.getExtendedErrorInformation();
                if (extendedError != null) {
                    bld.append(", ExtendedErrorInformation= {ErrorMessage= ");
                    bld.append(extendedError.getErrorMessage());
    
                    HashMap<String, String[]> details = extendedError.getAdditionalDetails();
                    if (details != null) {
                        bld.append(", AdditionalDetails= { ");
                        for (Entry<String, String[]> detail : details.entrySet()) {
                            bld.append(detail.getKey());
                            bld.append("= ");
    
                            for (String value : detail.getValue()) {
                                bld.append(value);
                            }
                            bld.append(",");
                        }
                        bld.setCharAt(bld.length() - 1, '}');
                    }
                    bld.append("}");
                }
    
                Logger.debug(opContext, bld.toString());
            } catch (Exception e) {
                // Do nothing
            }
        }
    }

    /**
     * Logs the HttpURLConnection request. If an exception is encountered, logs nothing.
     * 
     * @param conn
     *            The HttpURLConnection to serialize.
     * @param opContext 
     *            The operation context which provides the logger.
     */
    public static void logHttpRequest(HttpURLConnection conn, OperationContext opContext) throws IOException {
        if (Logger.shouldLog(opContext, Log.VERBOSE)) {
            try {
                StringBuilder bld = new StringBuilder();
    
                bld.append(conn.getRequestMethod());
                bld.append(" ");
                bld.append(conn.getURL());
                bld.append("\n");
    
                // The Authorization header will not appear due to a security feature in HttpURLConnection
                for (Map.Entry<String, List<String>> header : conn.getRequestProperties().entrySet()) {
                    if (header.getKey() != null) {
                        bld.append(header.getKey());
                        bld.append(": ");
                    }
    
                    for (int i = 0; i < header.getValue().size(); i++) {
                        bld.append(header.getValue().get(i));
                        if (i < header.getValue().size() - 1) {
                            bld.append(",");
                        }
                    }
                    bld.append('\n');
                }
    
                Logger.verbose(opContext, bld.toString());
            } catch (Exception e) {
                // Do nothing
            }
        }
    }

    /**
     * Logs the HttpURLConnection response. If an exception is encountered, logs nothing.
     * 
     * @param conn
     *            The HttpURLConnection to serialize.
     * @param opContext 
     *            The operation context which provides the logger.
     */
    public static void logHttpResponse(HttpURLConnection conn, OperationContext opContext) throws IOException {
        if (Logger.shouldLog(opContext, Log.VERBOSE)) {
            try {
                StringBuilder bld = new StringBuilder();
    
                // This map's null key will contain the response code and message
                for (Map.Entry<String, List<String>> header : conn.getHeaderFields().entrySet()) {
                    if (header.getKey() != null) {
                        bld.append(header.getKey());
                        bld.append(": ");
                    }
    
                    for (int i = 0; i < header.getValue().size(); i++) {
                        bld.append(header.getValue().get(i));
                        if (i < header.getValue().size() - 1) {
                            bld.append(",");
                        }
                    }
                    bld.append('\n');
                }
    
                Logger.verbose(opContext, bld.toString());
            } catch (Exception e) {
                // Do nothing
            }
        }
    }

    /**
     * Trims the specified character from the end of a string.
     * 
     * @param value
     *            A <code>String</code> that represents the string to trim.
     * @param trimChar
     *            The character to trim from the end of the string.
     * 
     * @return The string with the specified character trimmed from the end.
     */
    protected static String trimEnd(final String value, final char trimChar) {
        int stopDex = value.length() - 1;
        while (stopDex > 0 && value.charAt(stopDex) == trimChar) {
            stopDex--;
        }

        return stopDex == value.length() - 1 ? value : value.substring(stopDex);
    }

    /**
     * Trims whitespace from the beginning of a string.
     * 
     * @param value
     *            A <code>String</code> that represents the string to trim.
     * 
     * @return The string with whitespace trimmed from the beginning.
     */
    public static String trimStart(final String value) {
        int spaceDex = 0;
        while (spaceDex < value.length() && value.charAt(spaceDex) == ' ') {
            spaceDex++;
        }

        return value.substring(spaceDex);
    }

    /**
     * Reads data from an input stream and writes it to an output stream, calculates the length of the data written, and
     * optionally calculates the MD5 hash for the data.
     * 
     * @param sourceStream
     *            An <code>InputStream</code> object that represents the input stream to use as the source.
     * @param outStream
     *            An <code>OutputStream</code> object that represents the output stream to use as the destination.
     * @param writeLength
     *            The number of bytes to read from the stream.
     * @param rewindSourceStream
     *            <code>true</code> if the input stream should be rewound <strong>before</strong> it is read; otherwise,
     *            <code>false</code>
     * @param calculateMD5
     *            <code>true</code> if an MD5 hash will be calculated; otherwise, <code>false</code>.
     * @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.
     * @param options
     *            A {@link RequestOptions} object that specifies any additional options for the request. Namely, the
     *            maximum execution time.
     * @return A {@link StreamMd5AndLength} object that contains the output stream length, and optionally the MD5 hash.
     * 
     * @throws IOException
     *             If an I/O error occurs.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStream, final OutputStream outStream,
            long writeLength, final boolean rewindSourceStream, final boolean calculateMD5, OperationContext opContext,
            final RequestOptions options) throws IOException, StorageException {
        return writeToOutputStream(sourceStream, outStream, writeLength, rewindSourceStream, calculateMD5, opContext,
                options, true);
    }

    /**
     * Reads data from an input stream and writes it to an output stream, calculates the length of the data written, and
     * optionally calculates the MD5 hash for the data.
     * 
     * @param sourceStream
     *            An <code>InputStream</code> object that represents the input stream to use as the source.
     * @param outStream
     *            An <code>OutputStream</code> object that represents the output stream to use as the destination.
     * @param writeLength
     *            The number of bytes to read from the stream.
     * @param rewindSourceStream
     *            <code>true</code> if the input stream should be rewound <strong>before</strong> it is read; otherwise,
     *            <code>false</code>
     * @param calculateMD5
     *            <code>true</code> if an MD5 hash will be calculated; otherwise, <code>false</code>.
     * @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.
     * @param options
     *            A {@link RequestOptions} object that specifies any additional options for the request. Namely, the
     *            maximum execution time.
     * @return A {@link StreamMd5AndLength} object that contains the output stream length, and optionally the MD5 hash.
     * 
     * @throws IOException
     *             If an I/O error occurs.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStream, final OutputStream outStream,
            long writeLength, final boolean rewindSourceStream, final boolean calculateMD5, OperationContext opContext,
            final RequestOptions options, final Boolean shouldFlush) throws IOException, StorageException {
        return writeToOutputStream(sourceStream, outStream, writeLength, rewindSourceStream, calculateMD5, opContext,
                options, null /*StorageRequest*/, null /* descriptor */);
    }

    /**
     * Reads data from an input stream and writes it to an output stream, calculates the length of the data written, and
     * optionally calculates the MD5 hash for the data.
     * 
     * @param sourceStream
     *            An <code>InputStream</code> object that represents the input stream to use as the source.
     * @param outStream
     *            An <code>OutputStream</code> object that represents the output stream to use as the destination.
     * @param writeLength
     *            The number of bytes to read from the stream.
     * @param rewindSourceStream
     *            <code>true</code> if the input stream should be rewound <strong>before</strong> it is read; otherwise,
     *            <code>false</code>
     * @param calculateMD5
     *            <code>true</code> if an MD5 hash will be calculated; otherwise, <code>false</code>.
     * @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.
     * @param options
     *            A {@link RequestOptions} object that specifies any additional options for the request. Namely, the
     *            maximum execution time.
     * @param request
     *            Used by download resume to set currentRequestByteCount on the request. Otherwise, null is always used.
     * @param descriptor
     *                A {@Link StreamMd5AndLength} object to append to in the case of recovery action or null if this is not called
     *                from a recovery. This value needs to be passed for recovery in case part of the body has already been read,
     *                the recovery will attempt to download the remaining bytes but will do MD5 validation on the originally
     *                requested range size.
     * @return A {@link StreamMd5AndLength} object that contains the output stream length, and optionally the MD5 hash.
     * 
     * @throws IOException
     *             If an I/O error occurs.
     * @throws StorageException
     *             If a storage service error occurred.
     */
    public static StreamMd5AndLength writeToOutputStream(final InputStream sourceStream, final OutputStream outStream,
            long writeLength, final boolean rewindSourceStream, final boolean calculateMD5, OperationContext opContext,
            final RequestOptions options, StorageRequest<?, ?, Integer> request, StreamMd5AndLength descriptor)
            throws IOException, StorageException {
        if (rewindSourceStream && sourceStream.markSupported()) {
            sourceStream.reset();
            sourceStream.mark(Constants.MAX_MARK_LENGTH);
        }

        if (descriptor == null) {
            descriptor = new StreamMd5AndLength();
            if (calculateMD5) {
                try {
                    descriptor.setDigest(MessageDigest.getInstance("MD5"));
                }
                catch (final NoSuchAlgorithmException e) {
                    // This wont happen, throw fatal.
                    throw Utility.generateNewUnexpectedStorageException(e);
                }
            }
        }
        else {
            descriptor.setMd5(null);
        }

        if (writeLength < 0) {
            writeLength = Long.MAX_VALUE;
        }

        final byte[] retrievedBuff = new byte[Constants.BUFFER_COPY_LENGTH];
        int nextCopy = (int) Math.min(retrievedBuff.length, writeLength);
        int count = sourceStream.read(retrievedBuff, 0, nextCopy);

        while (nextCopy > 0 && count != -1) {

            // if maximum execution time would be exceeded
            if (Utility.validateMaxExecutionTimeout(options.getOperationExpiryTimeInMs())) {
                // throw an exception
                TimeoutException timeoutException = new TimeoutException(SR.MAXIMUM_EXECUTION_TIMEOUT_EXCEPTION);
                throw Utility.initIOException(timeoutException);
            }

            if (outStream != null) {
                outStream.write(retrievedBuff, 0, count);
            }

            if (calculateMD5) {
                descriptor.getDigest().update(retrievedBuff, 0, count);
            }

            descriptor.setLength(descriptor.getLength() + count);
            descriptor.setCurrentOperationByteCount(descriptor.getCurrentOperationByteCount() + count);

            if (request != null) {
                request.setCurrentRequestByteCount(request.getCurrentRequestByteCount() + count);
                request.setCurrentDescriptor(descriptor);
            }

            nextCopy = (int) Math.min(retrievedBuff.length, writeLength - descriptor.getLength());
            count = sourceStream.read(retrievedBuff, 0, nextCopy);
        }

        if (outStream != null) {
            outStream.flush();
        }

        return descriptor;
    }

    /**
     * Private Default Constructor.
     */
    private Utility() {
        // No op
    }

    public static void checkNullaryCtor(Class<?> clazzType) {
        Constructor<?> ctor = null;
        try {
            ctor = clazzType.getDeclaredConstructor((Class<?>[]) null);
        }
        catch (Exception e) {
            throw new IllegalArgumentException(SR.MISSING_NULLARY_CONSTRUCTOR);
        }

        if (ctor == null) {
            throw new IllegalArgumentException(SR.MISSING_NULLARY_CONSTRUCTOR);
        }
    }

    /**
     * Given a String representing a date in a form of the ISO8601 pattern, generates a Date representing it
     * with up to millisecond precision. 
     * 
     * @param dateString
     *              the <code>String</code> to be interpreted as a <code>Date</code>
     *              
     * @return the corresponding <code>Date</code> object
     */
    public static Date parseDate(String dateString) {
        String pattern = MAX_PRECISION_PATTERN;
        switch(dateString.length()) {
            case 28: // "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'"-> [2012-01-04T23:21:59.1234567Z] length = 28
            case 27: // "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"-> [2012-01-04T23:21:59.123456Z] length = 27
            case 26: // "yyyy-MM-dd'T'HH:mm:ss.SSSSS'Z'"-> [2012-01-04T23:21:59.12345Z] length = 26
            case 25: // "yyyy-MM-dd'T'HH:mm:ss.SSSS'Z'"-> [2012-01-04T23:21:59.1234Z] length = 25
            case 24: // "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"-> [2012-01-04T23:21:59.123Z] length = 24
                dateString = dateString.substring(0, MAX_PRECISION_DATESTRING_LENGTH);
                break;
            case 23: // "yyyy-MM-dd'T'HH:mm:ss.SS'Z'"-> [2012-01-04T23:21:59.12Z] length = 23
                // SS is assumed to be milliseconds, so a trailing 0 is necessary
                dateString = dateString.replace("Z", "0");
                break;
            case 22: // "yyyy-MM-dd'T'HH:mm:ss.S'Z'"-> [2012-01-04T23:21:59.1Z] length = 22
                // S is assumed to be milliseconds, so trailing 0's are necessary
                dateString = dateString.replace("Z", "00");
                break;
            case 20: // "yyyy-MM-dd'T'HH:mm:ss'Z'"-> [2012-01-04T23:21:59Z] length = 20
                pattern = Utility.ISO8601_PATTERN;
                break;
            case 17: // "yyyy-MM-dd'T'HH:mm'Z'"-> [2012-01-04T23:21Z] length = 17
                pattern = Utility.ISO8601_PATTERN_NO_SECONDS;
                break;
            default:
                throw new IllegalArgumentException(String.format(SR.INVALID_DATE_STRING, dateString));
        }

        final DateFormat format = new SimpleDateFormat(pattern, Utility.LOCALE_US);
        format.setTimeZone(UTC_ZONE);
        try {
            return format.parse(dateString);
        }
        catch (final ParseException e) {
            throw new IllegalArgumentException(String.format(SR.INVALID_DATE_STRING, dateString), e);
        }
    }
    
    /**
     * Given a String representing a date in a form of the ISO8601 pattern, generates a Date representing it
     * with up to millisecond precision. Use {@link #parseDate(String)} instead unless
     * <code>dateBackwardCompatibility</code> is needed.
     * <p>
     * See <a href="http://go.microsoft.com/fwlink/?LinkId=523753">here</a> for more details.
     * 
     * @param dateString
     *              the <code>String</code> to be interpreted as a <code>Date</code>
     * @param dateBackwardCompatibility
     *              <code>true</code> to correct Date values that may have been written
     *              using versions of this library prior to 0.4.0; otherwise, <code>false</code>
     *              
     * @return the corresponding <code>Date</code> object
     */
    public static Date parseDate(String dateString, boolean dateBackwardCompatibility) {
        if (!dateBackwardCompatibility) {
            return parseDate(dateString);
        }

        final int beginMilliIndex = 20;     // Length of "yyyy-MM-ddTHH:mm:ss."
        final int endTenthMilliIndex = 24;  // Length of "yyyy-MM-ddTHH:mm:ss.SSSS"

        // Check whether the millisecond and tenth of a millisecond digits are all 0.
        if (dateString.length() > endTenthMilliIndex &&
                "0000".equals(dateString.substring(beginMilliIndex, endTenthMilliIndex))) {
            // Remove the millisecond and tenth of a millisecond digits.
            // Treat the final three digits (ticks) as milliseconds.
            dateString = dateString.substring(0, beginMilliIndex) + dateString.substring(endTenthMilliIndex);
        }

        return parseDate(dateString);
    }

    /**
     * Determines which location can the listing command target by looking at the
     * continuation token.
     * 
     * @param token
     *            Continuation token
     * @return
     *         Location mode
     */
    public static RequestLocationMode getListingLocationMode(ResultContinuation token) {
        if ((token != null) && token.getTargetLocation() != null) {
            switch (token.getTargetLocation()) {
                case PRIMARY:
                    return RequestLocationMode.PRIMARY_ONLY;

                case SECONDARY:
                    return RequestLocationMode.SECONDARY_ONLY;

                default:
                    throw new IllegalArgumentException(String.format(SR.ARGUMENT_OUT_OF_RANGE_ERROR, "token",
                            token.getTargetLocation()));
            }
        }

        return RequestLocationMode.PRIMARY_OR_SECONDARY;
    }

    /**
     * Writes an XML element to the <code>XmlSerializer</code> without a namespace.
     * 
     * @param serializer
     *            the <code>XmlSerializer</code> to write to
     * @param tag
     *            the tag to use for the XML element
     * @param text
     *            the text to write inside the XML tags
     * @throws IOException
     *             if there is an error writing the output.
     * @throws IllegalStateException
     *             if there is an error writing the output.
     * @throws IllegalArgumentException
     *             if there is an error writing the output.
     */
    public static void serializeElement(XmlSerializer serializer, String tag, String text) throws IllegalArgumentException,
            IllegalStateException, IOException {
        serializer.startTag(Constants.EMPTY_STRING, tag);
        serializer.text(text);
        serializer.endTag(Constants.EMPTY_STRING, tag);
    }
}
import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.NameValidator;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.RetryNoRetry;
import com.microsoft.azure.storage.SendingRequestEvent;
import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature;
import com.microsoft.azure.storage.StorageErrorCodeStrings;
import com.microsoft.azure.storage.StorageEvent;
import com.microsoft.azure.storage.StorageException;
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.CloudBlob;
import com.microsoft.azure.storage.blob.CloudBlobContainer;
import com.microsoft.azure.storage.blob.CloudBlockBlob;
import com.microsoft.azure.storage.blob.CloudPageBlob;
import com.microsoft.azure.storage.blob.SharedAccessBlobPermissions;
import com.microsoft.azure.storage.blob.SharedAccessBlobPolicy;
import com.microsoft.azure.storage.core.Base64;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.UriQueryBuilder;
import com.microsoft.azure.storage.core.Utility;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;
import java.util.TimeZone;
import static org.junit.Assert.*;
        }
        catch (StorageException ex) {
            assertEquals("The specified resource does not exist.", ex.getMessage());
            assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode());
        }

        file.openWriteNew(1024);
        file.openWriteExisting();

        CloudFile file2 = this.share.getRootDirectoryReference().getFileReference(fileName);
        file2.downloadAttributes();
        assertEquals(1024, file2.getProperties().getLength());
    }

    /**
     * Test specific deleteIfExists case.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudFileDeleteIfExistsErrorCode() throws URISyntaxException, StorageException, IOException {
        CloudFileClient client = FileTestHelper.createCloudFileClient();
        CloudFileShare share = client.getShareReference(FileTestHelper.generateRandomShareName());
        share.create();
        CloudFileDirectory directory = share.getRootDirectoryReference().getDirectoryReference("directory");
        directory.create();
        final CloudFile file = directory.getFileReference("file");

        try {
            file.delete();
            fail("File should not already exist.");
        }
        catch (StorageException e) {
            assertEquals(StorageErrorCodeStrings.RESOURCE_NOT_FOUND, e.getErrorCode());
        }

        assertFalse(file.exists());
        assertFalse(file.deleteIfExists());

        file.create(2);
        assertTrue(file.exists());

        assertTrue(file.deleteIfExists());
        assertFalse(file.deleteIfExists());

        // check if second condition works in delete if exists
        OperationContext ctx = new OperationContext();
        ctx.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                if (((HttpURLConnection) eventArg.getConnectionObject()).getRequestMethod().equals("DELETE")) {
                    try {
                        file.delete();
                        assertFalse(file.exists());
                    }
                    catch (Exception e) {
                        fail("Delete should succeed.");
                    }
                }
            }
        });

        // The second delete of a file will return a 404
        file.create(2);
        assertFalse(file.deleteIfExists(null, null, ctx));
    }
    
    /**
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testFileNamePlusEncoding() throws StorageException, URISyntaxException, IOException,
            InterruptedException {
        CloudFile originalFile = FileTestHelper.uploadNewFile(this.share, 1024 /* length */, null);
        CloudFile copyFile = this.share.getRootDirectoryReference().getFileReference(originalFile.getName() + "copyed");

        copyFile.startCopy(originalFile);
        FileTestHelper.waitForCopy(copyFile);
        
        copyFile.downloadAttributes();
        originalFile.downloadAttributes();
        FileProperties prop1 = copyFile.getProperties();
        FileProperties prop2 = originalFile.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testFileGetRangeContentMD5Bounds() throws StorageException, IOException, URISyntaxException {
        {
            CloudFile file = FileTestHelper.uploadNewFile(this.share, 5 * Constants.MB, null);

            FileRequestOptions options = new FileRequestOptions();
            OperationContext opContext = new OperationContext();
            try {
                FileRequest.getFile(file.getUri(), options, opContext, null, null, 0L, 4L * Constants.MB, true);
                FileRequest.getFile(file.getUri(), options, opContext, null, null, 0L, 4L * Constants.MB + 1, true);
                fail("The request for range ContentMD5 should have thrown an Exception for exceeding the limit.");
            }
            catch (IllegalArgumentException e)
            {
                assertEquals(e.getMessage(), String.format("The value of the parameter 'count' should be between 1 and %1d.", Constants.MAX_BLOCK_SIZE));
            }
        }
    }
    
    private CloudFile doCloudBlobCopy(CloudBlob source, int length) throws Exception {