All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.windowsazure.storage.blob.CloudBlob Maven / Gradle / Ivy

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

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;

import com.microsoft.windowsazure.storage.AccessCondition;
import com.microsoft.windowsazure.storage.Constants;
import com.microsoft.windowsazure.storage.DoesServiceRequest;
import com.microsoft.windowsazure.storage.LeaseStatus;
import com.microsoft.windowsazure.storage.OperationContext;
import com.microsoft.windowsazure.storage.StorageCredentialsSharedAccessSignature;
import com.microsoft.windowsazure.storage.StorageErrorCodeStrings;
import com.microsoft.windowsazure.storage.StorageException;
import com.microsoft.windowsazure.storage.StorageLocation;
import com.microsoft.windowsazure.storage.StorageUri;
import com.microsoft.windowsazure.storage.core.Base64;
import com.microsoft.windowsazure.storage.core.ExecutionEngine;
import com.microsoft.windowsazure.storage.core.PathUtility;
import com.microsoft.windowsazure.storage.core.RequestLocationMode;
import com.microsoft.windowsazure.storage.core.SR;
import com.microsoft.windowsazure.storage.core.SharedAccessSignatureHelper;
import com.microsoft.windowsazure.storage.core.StorageRequest;
import com.microsoft.windowsazure.storage.core.StreamMd5AndLength;
import com.microsoft.windowsazure.storage.core.UriQueryBuilder;
import com.microsoft.windowsazure.storage.core.Utility;

/**
 * Represents a Windows Azure blob. This is the base class for the {@link CloudBlockBlob} and {@link CloudPageBlob}
 * classes.
 */
public abstract class CloudBlob implements ListBlobItem {
    /**
     * Holds the metadata for the blob.
     */
    HashMap metadata;

    /**
     * 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 blobs name.
     */
    private String name;

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

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

    /**
     * Represents the blob client.
     */
    protected CloudBlobClient blobServiceClient;

    /**
     * Creates an instance of the CloudBlob class.
     * 
     * @param type
     *            the type of the blob.
     */
    protected CloudBlob(final BlobType type) {
        this.metadata = new HashMap();
        this.properties = new BlobProperties(type);
    }

    /**
     * Creates an instance of the CloudBlob class using the specified URI and cloud blob client.
     * 
     * @param type
     *            the type of the blob.
     * @param uri
     *            A {@link StorageUri} object that represents the URI to the blob, beginning with the container
     *            name.
     * @param client
     *            A {@link CloudBlobClient} object that specifies the endpoint for the Blob service.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    protected CloudBlob(final BlobType type, final StorageUri uri, final CloudBlobClient client)
            throws StorageException {
        this(type);

        Utility.assertNotNull("blobAbsoluteUri", uri);

        this.blobServiceClient = client;
        this.storageUri = uri;

        this.parseURIQueryStringAndVerify(
                this.storageUri,
                client,
                client == null ? Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri(), null) : client
                        .isUsePathStyleUris());
    }

    /**
     * Creates an instance of the CloudBlob class using the specified URI, cloud blob client, and cloud
     * blob container.
     * 
     * @param type
     *            the type of the blob.
     * @param uri
     *            A {@link StorageUri} object that represents the absolute URI to the blob, beginning with the
     *            container name.
     * @param client
     *            A {@link CloudBlobClient} object that specifies the endpoint for the Blob service.
     * @param container
     *            A {@link CloudBlobContainer} object that represents the container to use for the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    protected CloudBlob(final BlobType type, final StorageUri uri, final CloudBlobClient client,
            final CloudBlobContainer container) throws StorageException {
        this(type, uri, client);
        this.container = container;
    }

    /**
     * Creates an instance of the CloudBlob class using the specified URI, snapshot ID, and cloud blob
     * client.
     * 
     * @param type
     *            the type of the blob.
     * @param uri
     *            A {@link StorageUri} object that represents the absolute URI to the blob, beginning with the
     *            container name.
     * @param snapshotID
     *            A String that represents the snapshot version, if applicable.
     * @param client
     *            A {@link CloudBlobContainer} object that represents the container to use for the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    protected CloudBlob(final BlobType type, final StorageUri uri, final String snapshotID, final CloudBlobClient client)
            throws StorageException {
        this(type, uri, client);
        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 CloudBlob class by copying values from another blob.
     * 
     * @param otherBlob
     *            A CloudBlob object that represents the blob to copy.
     */
    protected CloudBlob(final CloudBlob otherBlob) {
        this.metadata = new HashMap();
        this.properties = new BlobProperties(otherBlob.properties);

        if (otherBlob.metadata != null) {
            this.metadata = new HashMap();
            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 String 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 String 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
     *            null will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * 
     */
    @DoesServiceRequest
    public 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.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest abortCopyImpl(final String copyId,
            final AccessCondition accessCondition, final BlobRequestOptions options) throws StorageException {
        Utility.assertNotNull("copyId", copyId);

        final StorageRequest putRequest = new StorageRequest(
                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.getTimeoutIntervalInMs(), copyId, accessCondition, options, context);
            }

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

            @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 lease on the blob with the specified lease time and proposed lease ID.
     * 
     * @param leaseTimeInSeconds
     *            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 String that represents the proposed lease ID for the new lease,
     *            or null if no lease ID is proposed.
     * 
     * @return A String 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
     *            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 String 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
     *            null will use the default request options from the associated service client
     *            ({@link CloudBlobClient}).
     * 
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. The context
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     * 
     * @return A String 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.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest acquireLeaseImpl(final Integer leaseTimeInSeconds,
            final String proposedLeaseId, final AccessCondition accessCondition, final BlobRequestOptions options)
            throws StorageException {
        final StorageRequest putRequest = new StorageRequest(
                options, this.getStorageUri()) {

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

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

            @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(), context);
            }
        };

        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);
        }
        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);

        }
    }

    /**
     * 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 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
     *            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
     *            null will use the default request options from the associated service client
     *            ({@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. 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.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest breakLeaseImpl(final Integer breakPeriodInSeconds,
            final AccessCondition accessCondition, final BlobRequestOptions options) throws StorageException {

        final StorageRequest putRequest = new StorageRequest(
                options, this.getStorageUri()) {

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

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

            @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(), context);

                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 String 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 String 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 String 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
     *            null will use the default request options from the associated service client
     *            ({@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. The context
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     * @return A String 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.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);

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

    private StorageRequest changeLeaseImpl(final String proposedLeaseId,
            final AccessCondition accessCondition, final BlobRequestOptions options) throws StorageException {
        final StorageRequest putRequest = new StorageRequest(
                options, this.getStorageUri()) {

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

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

            @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(), context);
            }
        };

        return putRequest;
    }

    /**
     * Requests the service to start copying a blob's contents, properties, and metadata to a new blob.
     * 
     * @param sourceBlob
     *            A CloudBlob object that represents the source blob to copy.
     * @return The copy ID associated with the copy operation.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     */
    @DoesServiceRequest
    public final String startCopyFromBlob(final CloudBlob sourceBlob) throws StorageException, URISyntaxException {
        return this.startCopyFromBlob(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 CloudBlob 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
     *            null will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     * @return The copy ID associated with the copy operation.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     * @throws URISyntaxException
     * 
     */
    @DoesServiceRequest
    public final String startCopyFromBlob(final CloudBlob sourceBlob, final AccessCondition sourceAccessCondition,
            final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException, URISyntaxException {
        Utility.assertNotNull("sourceBlob", sourceBlob);
        return this.startCopyFromBlob(
                sourceBlob.getServiceClient().getCredentials().transformUri(sourceBlob.getQualifiedUri()),
                sourceAccessCondition, destinationAccessCondition, options, opContext);

    }

    /**
     * Requests the service to start copying a blob's contents, properties, and metadata to a new blob.
     * 
     * @param source
     *            A URI The URI of a source blob.
     * @return The copy ID associated with the copy operation.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final String startCopyFromBlob(final URI source) throws StorageException {
        return this.startCopyFromBlob(source, 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 source
     *            A URI The URI of a source 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
     *            null will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     * @return The copy ID associated with the copy operation.
     * @throws StorageException
     *             If a storage service error occurred.
     * 
     */
    @DoesServiceRequest
    public final String startCopyFromBlob(final URI source, final AccessCondition sourceAccessCondition,
            final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        if (opContext == null) {
            opContext = new OperationContext();
        }

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

        return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
                this.startCopyFromBlobImpl(source, sourceAccessCondition, destinationAccessCondition, options),
                options.getRetryPolicyFactory(), opContext);
    }

    private StorageRequest startCopyFromBlobImpl(final URI source,
            final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition,
            final BlobRequestOptions options) throws StorageException {

        if (sourceAccessCondition != null && !Utility.isNullOrEmpty(sourceAccessCondition.getLeaseID())) {
            throw new IllegalArgumentException(SR.LEASE_CONDITION_ON_SOURCE);
        }

        final StorageRequest putRequest = new StorageRequest(
                options, this.getStorageUri()) {

            @Override
            public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
                    throws Exception {
                return BlobRequest.copyFrom(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
                        options.getTimeoutIntervalInMs(), source.toString(), blob.snapshotID, sourceAccessCondition,
                        destinationAccessCondition, options, context);
            }

            @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.signBlobAndQueueRequest(connection, client, 0, null);
            }

            @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()));

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

        return putRequest;
    }

    /**
     * Creates a snapshot of the blob.
     * 
     * @return A CloudBlob 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
     *            null will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     * 
     * @return A CloudBlob 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
     *            null will use the default request options from the associated service client (
     *            {@link CloudBlobClient}).
     * @param opContext
     *            An {@link OperationContext} object that represents the context for the current operation. This object
     *            is used to track requests to the storage service, and to provide additional runtime information about
     *            the operation.
     * 
     * @return A CloudBlob object that represents the snapshot of the blob.
     * 
     * @throws StorageException
     *             If a storage service error occurred.
     */
    @DoesServiceRequest
    public final CloudBlob createSnapshot(final HashMap metadata,
            final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
            throws StorageException {
        assertNoWriteOperationForSnapshot();

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

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

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

    private StorageRequest createSnapshotImpl(
            final HashMap metadata, final AccessCondition accessCondition,
            final BlobRequestOptions options) throws StorageException {
        final StorageRequest putRequest = new StorageRequest(
                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.getTimeoutIntervalInMs(), accessCondition, options, context);
            }

            @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.signBlobAndQueueRequest(connection, client, 0L, null);
            }

            @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(), context);
                if (blob instanceof CloudBlockBlob) {
                    snapshot = new CloudBlockBlob(blob.getStorageUri(), snapshotTime, client);
                }
                else if (blob instanceof CloudPageBlob) {
                    snapshot = new CloudPageBlob(blob.getStorageUri(), snapshotTime, client);
                }

                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.
     * 

* 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 deleteSnapshotsOption 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 blobs, only snapshots, or * both. * @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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest public 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.applyDefaults(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. *

* 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 deleteSnapshotsOption parameter to specify how the snapshots should be handled when the blob * is deleted. * * @return true if the blob was deleted; otherwise, false. * * @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. *

* 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 deleteSnapshotsOption 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 blobs, only snapshots, or * both. * @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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @return true if the blob existed and was deleted; otherwise, false * * @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 { 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 deleteImpl( final DeleteSnapshotsOption deleteSnapshotsOption, final AccessCondition accessCondition, final BlobRequestOptions options) throws StorageException { final StorageRequest deleteRequest = new StorageRequest( options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) throws Exception { return BlobRequest.delete(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options.getTimeoutIntervalInMs(), blob.snapshotID, deleteSnapshotsOption, accessCondition, options, context); } @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { StorageRequest.signBlobAndQueueRequest(connection, client, -1L, null); } @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 OutputStream object that represents the target stream. * * @throws IOException * If an I/O exception occurred. * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest public final void download(final OutputStream outStream) throws StorageException, IOException { 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 OutputStream 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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws IOException * If an I/O exception occurred. * @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, IOException { if (opContext == null) { opContext = new OperationContext(); } opContext.initialize(); options = BlobRequestOptions.applyDefaults(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 * The offset to use as the starting point for the source. * @param length * The number of bytes to read or null. * @param outStream * An OutputStream object that represents the target stream. * * @throws IOException * If an I/O exception occurred. * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest public final void downloadRange(final long offset, final Long length, final OutputStream outStream) throws StorageException, IOException { 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 * The offset to use as the starting point for the source. * @param length * The number of bytes to read or null. * @param outStream * An OutputStream 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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws IOException * If an I/O exception occurred. * @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, IOException { if (offset < 0 || (length != null && length <= 0)) { throw new IndexOutOfBoundsException(); } if (opContext == null) { opContext = new OperationContext(); } opContext.initialize(); options = BlobRequestOptions.applyDefaults(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. *

* This method populates the blob's system properties and user-defined metadata. Before reading 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 Windows 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. *

* This method populates the blob's system properties and user-defined metadata. Before reading 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 Windows 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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest public final void downloadAttributes(final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.downloadAttributesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); } private StorageRequest downloadAttributesImpl( final AccessCondition accessCondition, final BlobRequestOptions options) throws StorageException { final StorageRequest getRequest = new StorageRequest( 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.getProperties(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options.getTimeoutIntervalInMs(), blob.snapshotID, accessCondition, options, context); } @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { StorageRequest.signBlobAndQueueRequest(connection, client, -1L, null); } @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.getAttributes(this.getConnection(), blob.getStorageUri(), blob.snapshotID, context); 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 downloadToStreamImpl(final Long blobOffset, final Long length, final OutputStream outStream, final AccessCondition accessCondition, final BlobRequestOptions options, OperationContext opContext) throws StorageException, IOException { final long startingOffset = blobOffset == null ? 0 : blobOffset; final boolean isRangeGet = blobOffset != null; final StorageRequest getRequest = new StorageRequest( 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.getBlobOffset() == null) { this.setBlobOffset(blobOffset); } if (this.getLength() == null) { this.setLength(length); } AccessCondition tempCondition = (this.getETagLockCondition() != null) ? this.getETagLockCondition() : accessCondition; return BlobRequest.get(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options.getTimeoutIntervalInMs(), blob.snapshotID, this.getBlobOffset(), this.getLength(), (options.getUseTransactionalContentMD5() && !this.getArePropertiesPopulated()), tempCondition, options, context); } @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { StorageRequest.signBlobAndQueueRequest(connection, client, -1L, null); } @Override public Integer preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context) throws Exception { return preProcessDownloadResponse(this, options, client, blob, context, isRangeGet); } @Override public Integer postProcessResponse(HttpURLConnection connection, CloudBlob blob, CloudBlobClient client, OperationContext context, Integer storageObject) throws Exception { final InputStream streamRef = connection.getInputStream(); final Boolean validateMD5 = !options.getDisableContentMD5Validation() && !Utility.isNullOrEmpty(this.getContentMD5()); final String contentLength = connection.getHeaderField(Constants.HeaderConstants.CONTENT_LENGTH); final long expectedLength = Long.parseLong(contentLength); final StreamMd5AndLength descriptor = Utility.writeToOutputStream(streamRef, outStream, -1, false, validateMD5, context); this.setCurrentRequestByteCount(this.getCurrentRequestByteCount() + context.getCurrentOperationByteCount()); if (descriptor.getLength() != expectedLength) { throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, SR.CONTENT_LENGTH_MISMATCH, Constants.HeaderConstants.HTTP_UNUSED_306, null, null); } 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); } 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.setBlobOffset(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 * the offset of the blob to begin downloading at * @param length * the number of bytes to read * @param buffer * the byte buffer to write to. * @param bufferOffset * 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 * An object that specifies any additional options for the request * @param opContext * an object used to track the execution of the operation * @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.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); if (options.getUseTransactionalContentMD5() && (length != null && length > 4 * Constants.MB)) { throw new IllegalArgumentException(SR.INVALID_RANGE_CONTENT_MD5_HEADER); } return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.downloadToByteArrayImpl(blobOffset, length, buffer, bufferOffset, accessCondition, options, opContext), options.getRetryPolicyFactory(), opContext); } /** * Downloads a range of bytes from the blob to the given byte buffer. * * @param offset * The byte offset to use as the starting point for the source. * @param length * The number of bytes to read or null. * @param buffer * The byte buffer, as an array of bytes, to which the blob bytes are downloaded. * @param bufferOffet * The byte offset to use as the starting point for the target. * * @throws StorageException */ @DoesServiceRequest public final int downloadRangeToByteArray(final long offset, final Long length, final byte[] buffer, final int bufferOffet) throws StorageException { return this.downloadRangeToByteArray(offset, length, buffer, bufferOffet, 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 * The byte offset to use as the starting point for the source. * @param length * The number of bytes to read or null. * @param buffer * The byte buffer, as an array of bytes, to which the blob bytes are downloaded. * @param bufferOffset * 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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest public 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 offset * The byte offset to use as the starting point for the source. * @param length * The number of bytes to read. * @param buffer * The byte buffer, as an array of bytes, to which the blob bytes are downloaded. * @param bufferOffet * 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 bufferOffet) throws StorageException { return this .downloadToByteArray(buffer, bufferOffet, 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 * The byte offset to use as the starting point for the source. * @param length * The number of bytes to read. * @param buffer * The byte buffer, as an array of bytes, to which the blob bytes are downloaded. * @param bufferOffet * 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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest public 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.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.downloadToByteArrayImpl(null, null, buffer, bufferOffset, accessCondition, options, opContext), options.getRetryPolicyFactory(), opContext); } private StorageRequest downloadToByteArrayImpl(final Long blobOffset, final Long length, final byte[] buffer, final int bufferOffset, final AccessCondition accessCondition, final BlobRequestOptions options, OperationContext opContext) throws StorageException { final long startingOffset = blobOffset == null ? 0 : blobOffset; final boolean isRangeGet = blobOffset != null; final StorageRequest getRequest = new StorageRequest( 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.getBlobOffset() == null) { this.setBlobOffset(blobOffset); } if (this.getLength() == null) { this.setLength(length); } AccessCondition tempCondition = (this.getETagLockCondition() != null) ? this.getETagLockCondition() : accessCondition; return BlobRequest.get(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options.getTimeoutIntervalInMs(), blob.snapshotID, this.getBlobOffset(), this.getLength(), (options.getUseTransactionalContentMD5() && !this.getArePropertiesPopulated()), tempCondition, options, context); } @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { StorageRequest.signBlobAndQueueRequest(connection, client, -1L, null); } @Override public Integer preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context) throws Exception { return preProcessDownloadResponse(this, options, client, blob, context, isRangeGet); } @Override public Integer postProcessResponse(HttpURLConnection connection, CloudBlob blob, CloudBlobClient client, OperationContext context, Integer storageObject) throws Exception { final InputStream sourceStream = connection.getInputStream(); int totalRead = 0; int nextRead = buffer.length - bufferOffset; int count = sourceStream.read(buffer, bufferOffset, nextRead); while (count > 0) { totalRead += count; this.setCurrentRequestByteCount(this.getCurrentRequestByteCount() + count); nextRead = buffer.length - (bufferOffset + totalRead); if (nextRead == 0) { // check for case where more data is returned if (sourceStream.read(new byte[1], 0, 1) != -1) { throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, SR.CONTENT_LENGTH_MISMATCH, Constants.HeaderConstants.HTTP_UNUSED_306, null, null); } } count = sourceStream.read(buffer, bufferOffset + totalRead, nextRead); } final String contentLength = connection.getHeaderField(Constants.HeaderConstants.CONTENT_LENGTH); final long expectedLength = Long.parseLong(contentLength); if (totalRead != expectedLength) { throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, SR.CONTENT_LENGTH_MISMATCH, Constants.HeaderConstants.HTTP_UNUSED_306, null, null); } final Boolean validateMD5 = !options.getDisableContentMD5Validation() && !Utility.isNullOrEmpty(this.getContentMD5()); if (validateMD5) { try { final MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(buffer, bufferOffset, (int) this.getCurrentRequestByteCount()); final String calculatedMD5 = Base64.encode(digest.digest()); if (!this.getContentMD5().equals(calculatedMD5)) { throw new StorageException(StorageErrorCodeStrings.INVALID_MD5, String.format( SR.BLOB_HASH_MISMATCH, this.getContentMD5(), calculatedMD5), Constants.HeaderConstants.HTTP_UNUSED_306, null, null); } } catch (final NoSuchAlgorithmException e) { // This wont happen, throw fatal. throw Utility.generateNewUnexpectedStorageException(e); } } return (int) this.getCurrentRequestByteCount(); } @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.setBlobOffset(startingOffset + this.getCurrentRequestByteCount()); if (length != null) { this.setLength(length - this.getCurrentRequestByteCount()); } } } }; return getRequest; } /** * Uploads a blob from data in a byte array. * * @param buffer * The data to write to the blob. * @param offset * Offset of the byte array from which to start the data upload. * @param length * 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. * * @param buffer * The data to write to the blob. * @param offset * Offset of the byte array from which to start the data upload. * @param length * 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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws StorageException * If a storage service error occurred. * @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); } /** * Uploads a blob from a file. * * @param path * 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. * * @param path * 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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws StorageException * If a storage service error occurred. * @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); } /** * Downloads a blob, storing the contents in a file. * * @param path * 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 * 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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws StorageException * If a storage service error occurred. * @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)); this.download(outputStream, accessCondition, options, opContext); outputStream.close(); } /** * Checks to see if the blob exists. * * @return true if the blob exists, other wise false. * * @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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @return true if the blob exists, other wise false. * * @throws StorageException * f 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.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.existsImpl(primaryOnly, accessCondition, options), options.getRetryPolicyFactory(), opContext); } private StorageRequest existsImpl(final boolean primaryOnly, final AccessCondition accessCondition, final BlobRequestOptions options) throws StorageException { final StorageRequest getRequest = new StorageRequest( 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.getProperties(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options.getTimeoutIntervalInMs(), blob.snapshotID, accessCondition, options, context); } @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { StorageRequest.signBlobAndQueueRequest(connection, client, -1L, null); } @Override public Boolean preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context) throws Exception { if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) { final BlobAttributes retrievedAttributes = BlobResponse.getAttributes(this.getConnection(), blob.getStorageUri(), blob.snapshotID, context); 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 SharedAccessPolicy object that represents the access policy for the shared access * signature. * @param groupPolicyIdentifier * A String that represents the container-level access policy. * * @return A String 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 SharedAccessPolicy object that represents the access policy for the shared access * signature. * @param headers * The optional header values to set for a blob accessed with this shared access signature. * @param groupPolicyIdentifier * A String that represents the container-level access policy. * * @return A String 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 { if (!this.blobServiceClient.getCredentials().canCredentialsSignRequest()) { 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.generateSharedAccessSignatureHashForBlob(policy, headers, groupPolicyIdentifier, resourceName, this.blobServiceClient, null); final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForBlob(policy, headers, groupPolicyIdentifier, "b", signature); return builder.toString(); } /** * Returns the canonical name of the blob in the format of * /<account-name>/<container-name>/<blob-name>. *

* This format is used by both Shared Access and Copy blob operations. * * @param ignoreSnapshotTime * true if the snapshot time is ignored; otherwise, false. * * @return The canonical name in the format of /<account-name>/<container * -name>/<blob-name>. */ String getCanonicalName(final boolean ignoreSnapshotTime) { String canonicalName; if (this.blobServiceClient.isUsePathStyleUris()) { canonicalName = this.getUri().getRawPath(); } else { canonicalName = PathUtility.getCanonicalPathFromCredentials(this.blobServiceClient.getCredentials(), this .getUri().getRawPath()); } if (!ignoreSnapshotTime && this.snapshotID != null) { canonicalName = canonicalName.concat("?snapshot="); canonicalName = canonicalName.concat(this.snapshotID); } return canonicalName; } /** * 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); } return this.container; } /** * Returns the metadata for the blob. * * @return A java.util.HashMap object that represents the metadata for the blob. */ public final HashMap getMetadata() { return this.metadata; } /** * Returns the name of the blob. * * @return A String that represents the name of the blob. * * @throws URISyntaxException * If the resource URI is invalid. */ public final String getName() throws URISyntaxException { if (Utility.isNullOrEmpty(this.name)) { this.name = PathUtility.getBlobNameFromURI(this.getUri(), this.blobServiceClient.isUsePathStyleUris()); } 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 StorageUri parentURI = PathUtility.getParentAddress(this.getStorageUri(), this.blobServiceClient.getDirectoryDelimiter(), this.blobServiceClient.isUsePathStyleUris()); if (parentURI != null) { this.parent = new CloudBlobDirectory(parentURI, null, this.blobServiceClient); } } 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 snapshot or shared access signature qualified URI for this blob. * * @return A 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. */ 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. * * @return A java.net.URI 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. */ 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 the Blob Snapshot ID. */ public final String getSnapshotID() { return this.snapshotID; } /** * Returns the list of URIs for all locations. * * @return A 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 * 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 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 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 { if (this.blobServiceClient.getCredentials().doCredentialsNeedTransformUri()) { if (this.getStorageUri().isAbsolute()) { return this.blobServiceClient.getCredentials().transformUri(this.getStorageUri(), opContext); } else { final StorageException ex = Utility.generateNewUnexpectedStorageException(null); ex.getExtendedErrorInformation().setErrorMessage("Blob Object relative URIs not supported."); throw ex; } } else { return this.getStorageUri(); } } /** * Returns the URI for this blob. * * @return A java.net.URI 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 true if the blob is a snapshot, otherwise false. * * @see DeleteSnapshotsOption */ public final boolean isSnapshot() { return this.snapshotID != null; } /** * Opens a blob input stream to download the blob. *

* Use {@link CloudBlobClient#setStreamMinimumReadSizeInBytes} to configure the read size. * * @return An InputStream 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. *

* Use {@link CloudBlobClient#setStreamMinimumReadSizeInBytes} 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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @return An InputStream 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.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); return new BlobInputStream(this, accessCondition, options, opContext); } /** * Parse Uri for SAS (Shared access signature) information. * * Validate that no other query parameters are passed in. Any SAS information will be recorded as corresponding * credentials instance. If existingClient is passed in, any SAS information found will not be supported. Otherwise * a new client is created based on SAS information or as anonymous credentials. * * @param completeUri * The complete Uri. * @param existingClient * The client to use. * @param usePathStyleUris * If true, path style Uris are used. * @throws StorageException * If a storage service error occurred. * */ protected void parseURIQueryStringAndVerify(final StorageUri completeUri, final CloudBlobClient existingClient, final boolean usePathStyleUris) throws StorageException { Utility.assertNotNull("resourceUri", completeUri); if (!completeUri.isAbsolute()) { final String errorMessage = String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString()); throw new IllegalArgumentException(errorMessage); } this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri); final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); final StorageCredentialsSharedAccessSignature sasCreds = SharedAccessSignatureHelper .parseQuery(queryParameters); final String[] snapshotIDs = queryParameters.get(BlobConstants.SNAPSHOT); if (snapshotIDs != null && snapshotIDs.length > 0) { this.snapshotID = snapshotIDs[0]; } if (sasCreds == null) { return; } final Boolean sameCredentials = existingClient == null ? false : Utility.areCredentialsEqual(sasCreds, existingClient.getCredentials()); if (existingClient == null || !sameCredentials) { try { this.blobServiceClient = new CloudBlobClient((PathUtility.getServiceClientBaseAddress( this.getStorageUri(), usePathStyleUris)), sasCreds); } catch (final URISyntaxException e) { throw Utility.generateNewUnexpectedStorageException(e); } } if (existingClient != null && !sameCredentials) { this.blobServiceClient.setSingleBlobPutThresholdInBytes(existingClient.getSingleBlobPutThresholdInBytes()); this.blobServiceClient.setConcurrentRequestCount(existingClient.getConcurrentRequestCount()); this.blobServiceClient.setDirectoryDelimiter(existingClient.getDirectoryDelimiter()); this.blobServiceClient.setRetryPolicyFactory(existingClient.getRetryPolicyFactory()); this.blobServiceClient.setTimeoutInMs(existingClient.getTimeoutInMs()); this.blobServiceClient.setLocationMode(existingClient.getLocationMode()); } } /** * 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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest public 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.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.releaseLeaseImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); } private StorageRequest releaseLeaseImpl(final AccessCondition accessCondition, final BlobRequestOptions options) throws StorageException { final StorageRequest putRequest = new StorageRequest( options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) throws Exception { return BlobRequest .lease(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options.getTimeoutIntervalInMs(), LeaseAction.RELEASE, null /* leaseTimeInSeconds */, null /* proposedLeaseId */, null /*breakPeriodInSeconds */, accessCondition, options, context); } @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { StorageRequest.signBlobAndQueueRequest(connection, client, 0L, null); } @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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest public 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.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.renewLeaseImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); } private StorageRequest renewLeaseImpl(final AccessCondition accessCondition, final BlobRequestOptions options) throws StorageException { final StorageRequest putRequest = new StorageRequest( options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) throws Exception { return BlobRequest .lease(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options.getTimeoutIntervalInMs(), LeaseAction.RENEW, null /* leaseTimeInSeconds */, null /* proposedLeaseId */, null /*breakPeriodInSeconds */, accessCondition, options, context); } @Override public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) throws Exception { StorageRequest.signBlobAndQueueRequest(connection, client, 0L, null); } @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; } /** * 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 java.util.HashMap object that contains the metadata being assigned to the blob. */ public final void setMetadata(final HashMap 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 String 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 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 * 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 * 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 minimumReadSize 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. * * @param sourceStream * An InputStream 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. * * @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. * * @param sourceStream * An InputStream 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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws 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 a blob in a single operation. * * @param sourceStream * A InputStream 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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws IOException * If an I/O exception occurred. * @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, IOException { 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 uploadFullBlobImpl(final InputStream sourceStream, final long length, final AccessCondition accessCondition, final BlobRequestOptions options, final OperationContext opContext) { final StorageRequest putRequest = new StorageRequest( options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) throws Exception { this.setSendStream(sourceStream); this.setLength(length); return BlobRequest.put(blob.getTransformedAddress(opContext).getUri(this.getCurrentLocation()), options.getTimeoutIntervalInMs(), blob.properties, blob.properties.getBlobType(), this.getLength(), accessCondition, options, opContext); } @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.signBlobAndQueueRequest(connection, client, length, null); } @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()); 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 the blob's metadata to the 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. * * @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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest public final void uploadMetadata(final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { assertNoWriteOperationForSnapshot(); if (opContext == null) { opContext = new OperationContext(); } opContext.initialize(); options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.uploadMetadataImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); } private StorageRequest uploadMetadataImpl(final AccessCondition accessCondition, final BlobRequestOptions options) throws StorageException { final StorageRequest putRequest = new StorageRequest( options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) throws Exception { return BlobRequest.setMetadata(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options.getTimeoutIntervalInMs(), accessCondition, options, context); } @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.signBlobAndQueueRequest(connection, client, 0L, null); } @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; } /** * Updates the blob's properties to the 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. * * @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 * null will use the default request options from the associated service client ( * {@link CloudBlobClient}). * @param opContext * An {@link OperationContext} object that represents the context for the current operation. This object * is used to track requests to the storage service, and to provide additional runtime information about * the operation. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest public final void uploadProperties(final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { assertNoWriteOperationForSnapshot(); if (opContext == null) { opContext = new OperationContext(); } opContext.initialize(); options = BlobRequestOptions.applyDefaults(options, this.properties.getBlobType(), this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.uploadPropertiesImpl(accessCondition, options), options.getRetryPolicyFactory(), opContext); } private StorageRequest uploadPropertiesImpl( final AccessCondition accessCondition, final BlobRequestOptions options) throws StorageException { final StorageRequest putRequest = new StorageRequest( options, this.getStorageUri()) { @Override public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) throws Exception { return BlobRequest.setProperties(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options.getTimeoutIntervalInMs(), blob.properties, null, accessCondition, options, context); } @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.signBlobAndQueueRequest(connection, client, 0L, null); } @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; } private Integer preProcessDownloadResponse(final StorageRequest request, final BlobRequestOptions options, final CloudBlobClient client, final CloudBlob blob, final OperationContext context, final boolean isRangeGet) throws StorageException, URISyntaxException, ParseException { if (request.getResult().getStatusCode() != HttpURLConnection.HTTP_PARTIAL && request.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { request.setNonExceptionedRetryableFailure(true); return null; } if (!request.getArePropertiesPopulated()) { String originalContentMD5 = null; final BlobAttributes retrievedAttributes = BlobResponse.getAttributes(request.getConnection(), blob.getStorageUri(), blob.snapshotID, context); // Do not update Content-MD5 if it is a range get. if (isRangeGet) { originalContentMD5 = blob.properties.getContentMD5(); } else { originalContentMD5 = retrievedAttributes.getProperties().getContentMD5(); } if (!options.getDisableContentMD5Validation() && options.getUseTransactionalContentMD5() && Utility.isNullOrEmpty(retrievedAttributes.getProperties().getContentMD5())) { throw new StorageException(StorageErrorCodeStrings.MISSING_MD5_HEADER, SR.MISSING_MD5, Constants.HeaderConstants.HTTP_UNUSED_306, null, null); } blob.properties = retrievedAttributes.getProperties(); blob.metadata = retrievedAttributes.getMetadata(); request.setContentMD5(retrievedAttributes.getProperties().getContentMD5()); blob.properties.setContentMD5(originalContentMD5); request.setLockedETag(blob.properties.getEtag()); request.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. request.setRequestLocationMode(request.getResult().getTargetLocation() == StorageLocation.PRIMARY ? RequestLocationMode.PRIMARY_ONLY : RequestLocationMode.SECONDARY_ONLY); return null; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy