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