com.microsoft.azure.storage.blob.CloudBlob Maven / Gradle / Ivy
Show all versions of azure-storage Show documentation
/**
* Copyright Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.azure.storage.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.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import com.microsoft.azure.storage.AccessCondition;
import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.DoesServiceRequest;
import com.microsoft.azure.storage.IPRange;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.SharedAccessPolicy;
import com.microsoft.azure.storage.SharedAccessProtocols;
import com.microsoft.azure.storage.StorageCredentials;
import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature;
import com.microsoft.azure.storage.StorageErrorCodeStrings;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.StorageLocation;
import com.microsoft.azure.storage.StorageUri;
import com.microsoft.azure.storage.core.ExecutionEngine;
import com.microsoft.azure.storage.core.Logger;
import com.microsoft.azure.storage.core.NetworkInputStream;
import com.microsoft.azure.storage.core.PathUtility;
import com.microsoft.azure.storage.core.RequestLocationMode;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.SharedAccessSignatureHelper;
import com.microsoft.azure.storage.core.StorageCredentialsHelper;
import com.microsoft.azure.storage.core.StorageRequest;
import com.microsoft.azure.storage.core.StreamMd5AndLength;
import com.microsoft.azure.storage.core.UriQueryBuilder;
import com.microsoft.azure.storage.core.Utility;
import com.microsoft.azure.storage.core.WrappedByteArrayOutputStream;
/**
* Represents a Microsoft 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 = new HashMap();
/**
* Holds the properties of the blob.
*/
BlobProperties properties;
/**
* Holds the list of URIs for all locations.
*/
private StorageUri storageUri;
/**
* Holds the snapshot ID.
*/
String snapshotID;
/**
* Holds the blob's container reference.
*/
private CloudBlobContainer container;
/**
* Represents the blob's directory.
*/
protected CloudBlobDirectory parent;
/**
* Holds the blob's name.
*/
private String name;
/**
* Holds the number of bytes to buffer when writing to a {@link BlobOutputStream} (block and page blobs).
*/
protected int streamWriteSizeInBytes = Constants.DEFAULT_STREAM_WRITE_IN_BYTES;
/**
* Holds the minimum read size when using a {@link BlobInputStream}.
*/
protected int streamMinimumReadSizeInBytes = Constants.DEFAULT_MINIMUM_READ_SIZE_IN_BYTES;
/**
* Represents the blob client.
*/
protected CloudBlobClient blobServiceClient;
/**
* Creates an instance of the CloudBlob
class using the specified type, name, snapshot ID, and
* container.
*
* @param type
* A {@link BlobType} value which represents the type of the blob.
* @param blobName
* Name of the blob.
* @param snapshotID
* A String
that represents the snapshot version, if applicable.
* @param container
* The reference to the parent container.
* @throws URISyntaxException
* If the resource URI is invalid.
*/
protected CloudBlob(final BlobType type, String blobName, String snapshotID, CloudBlobContainer container)
throws URISyntaxException {
Utility.assertNotNullOrEmpty("blobName", blobName);
Utility.assertNotNull("container", container);
this.storageUri = PathUtility.appendPathToUri(container.getStorageUri(), blobName);
this.name = blobName;
this.blobServiceClient = container.getServiceClient();
this.container = container;
this.snapshotID = snapshotID;
this.properties = new BlobProperties(type);
}
/**
* Creates an instance of the CloudBlob
class using the specified URI, snapshot ID, and cloud blob
* client.
*
* @param type
* A {@link BlobType} value which represents the type of the blob.
* @param uri
* A {@link StorageUri} object that represents the URI to the blob, beginning with the container name.
* @param snapshotID
* A String
that represents the snapshot version, if applicable.
* @param credentials
* A {@link StorageCredentials} object used to authenticate access.
* @throws StorageException
* If a storage service error occurred.
*/
protected CloudBlob(final BlobType type, final StorageUri uri, final String snapshotID,
final StorageCredentials credentials) throws StorageException {
this.properties = new BlobProperties(type);
this.parseQueryAndVerify(uri, credentials);
if (snapshotID != null) {
if (this.snapshotID != null) {
throw new IllegalArgumentException(SR.SNAPSHOT_QUERY_OPTION_ALREADY_DEFINED);
}
else {
this.snapshotID = snapshotID;
}
}
}
/**
* Creates an instance of the 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.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.populateAndApplyDefaults(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) {
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, context, accessCondition, copyId);
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, 0, context);
}
@Override
public Void preProcessResponse(CloudBlob parentObject, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) {
this.setNonExceptionedRetryableFailure(true);
return null;
}
return null;
}
};
return putRequest;
}
/**
* Acquires a new infinite lease on the blob.
*
* @return A String
that represents the lease ID.
*
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final String acquireLease() throws StorageException {
return this.acquireLease(null /* leaseTimeInSeconds */, null /* proposedLeaseId */);
}
/**
* Acquires a new lease on the blob with the specified lease time and proposed lease ID.
*
* @param leaseTimeInSeconds
* An Integer
which specifies the span of time for which to acquire the lease, in seconds.
* If null, an infinite lease will be acquired. If not null, the value must be greater than
* zero.
*
* @param proposedLeaseId
* A 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
* An Integer
which specifies the span of time for which to acquire the lease, in seconds.
* If null, an infinite lease will be acquired. If not null, the value must be greater than
* zero.
*
* @param proposedLeaseId
* A 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.populateAndApplyDefaults(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) {
final StorageRequest putRequest = new StorageRequest(
options, this.getStorageUri()) {
@Override
public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
throws Exception {
return BlobRequest.leaseBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
options, context, accessCondition, LeaseAction.ACQUIRE, leaseTimeInSeconds, proposedLeaseId,
null);
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
}
@Override
public String preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) {
this.setNonExceptionedRetryableFailure(true);
return null;
}
updateEtagAndLastModifiedFromResponse(this.getConnection());
blob.properties.setLeaseStatus(LeaseStatus.LOCKED);
return BlobResponse.getLeaseID(this.getConnection());
}
};
return putRequest;
}
/**
* Asserts that the blob has the correct blob type specified in the blob attributes.
*
* @throws StorageException
* If an incorrect blob type is used.
*/
protected final void assertCorrectBlobType() throws StorageException {
if (this instanceof CloudBlockBlob && this.properties.getBlobType() != BlobType.BLOCK_BLOB) {
throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE, String.format(SR.INVALID_BLOB_TYPE,
BlobType.BLOCK_BLOB, this.properties.getBlobType()), Constants.HeaderConstants.HTTP_UNUSED_306,
null, null);
}
else if (this instanceof CloudPageBlob && this.properties.getBlobType() != BlobType.PAGE_BLOB) {
throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE, String.format(SR.INVALID_BLOB_TYPE,
BlobType.PAGE_BLOB, this.properties.getBlobType()), Constants.HeaderConstants.HTTP_UNUSED_306,
null, null);
}
else if (this instanceof CloudAppendBlob && this.properties.getBlobType() != BlobType.APPEND_BLOB) {
throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE, String.format(SR.INVALID_BLOB_TYPE,
BlobType.APPEND_BLOB, this.properties.getBlobType()), Constants.HeaderConstants.HTTP_UNUSED_306,
null, null);
}
}
/**
* Asserts that write operation is not done for snapshot.
*/
protected void assertNoWriteOperationForSnapshot() {
if (isSnapshot()) {
throw new IllegalArgumentException(SR.INVALID_OPERATION_FOR_A_SNAPSHOT);
}
}
/**
* Breaks the lease and ensures that another client cannot acquire a new lease until the current lease period
* has expired.
*
* @param breakPeriodInSeconds
* Specifies the time to wait, in seconds, until the current lease is broken.
* If null, the break period is the remainder of the current lease, or zero for infinite leases.
*
* @return An long
which specifies the time, in seconds, remaining in the lease period.
*
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final long breakLease(final Integer breakPeriodInSeconds) throws StorageException {
return this
.breakLease(breakPeriodInSeconds, null /* accessCondition */, null /* options */, null /* opContext */);
}
/**
* Breaks the existing lease, using the specified request options and operation context, and ensures that another
* client cannot acquire a new lease until the current lease period has expired.
*
* @param breakPeriodInSeconds
* An Integer
which specifies the time to wait, in seconds, until the current lease is
* broken.
* If null, the break period is the remainder of the current lease, or zero for infinite leases.
*
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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 An long
which represents the time, in seconds, remaining in the lease period.
*
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final long breakLease(final Integer breakPeriodInSeconds, final AccessCondition accessCondition,
BlobRequestOptions options, OperationContext opContext) throws StorageException {
if (opContext == null) {
opContext = new OperationContext();
}
if (breakPeriodInSeconds != null) {
Utility.assertGreaterThanOrEqual("breakPeriodInSeconds", breakPeriodInSeconds, 0);
}
opContext.initialize();
options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);
return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
this.breakLeaseImpl(breakPeriodInSeconds, accessCondition, options), options.getRetryPolicyFactory(),
opContext);
}
private StorageRequest breakLeaseImpl(final Integer breakPeriodInSeconds,
final AccessCondition accessCondition, final BlobRequestOptions options) {
final StorageRequest putRequest = new StorageRequest(
options, this.getStorageUri()) {
@Override
public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
throws Exception {
return BlobRequest.leaseBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
options, context, accessCondition, LeaseAction.BREAK, null, null, breakPeriodInSeconds);
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
}
@Override
public Long preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) {
this.setNonExceptionedRetryableFailure(true);
return -1L;
}
updateEtagAndLastModifiedFromResponse(this.getConnection());
final String leaseTime = BlobResponse.getLeaseTime(this.getConnection());
blob.properties.setLeaseStatus(LeaseStatus.UNLOCKED);
return Utility.isNullOrEmpty(leaseTime) ? -1L : Long.parseLong(leaseTime);
}
};
return putRequest;
}
/**
* Changes the existing lease ID to the proposed lease ID.
*
* @param proposedLeaseId
* A 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.populateAndApplyDefaults(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) {
final StorageRequest putRequest = new StorageRequest(
options, this.getStorageUri()) {
@Override
public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
throws Exception {
return BlobRequest.leaseBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
options, context, accessCondition, LeaseAction.CHANGE, null, proposedLeaseId, null);
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
}
@Override
public String preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
this.setNonExceptionedRetryableFailure(true);
return null;
}
updateEtagAndLastModifiedFromResponse(this.getConnection());
return BlobResponse.getLeaseID(this.getConnection());
}
};
return putRequest;
}
/**
* Requests the service to start copying a URI's contents, properties, and metadata to a new blob.
*
* @param source
* A java.net.URI
The source URI. URIs for resources outside of Azure
* may only be copied into block blobs.
*
* @return A String
which represents the copy ID associated with the copy operation.
*
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final String startCopy(final URI source) throws StorageException {
return this.startCopy(source, null /* sourceAccessCondition */, null /* destinationAccessCondition */,
null /* options */, null /* opContext */);
}
/**
* Requests the service to start copying a URI's contents, properties, and metadata to a new blob, using the
* specified access conditions, lease ID, request options, and operation context.
*
* @param source
* A java.net.URI
The source URI. URIs for resources outside of Azure
* may only be copied into block blobs.
* @param sourceAccessCondition
* An {@link AccessCondition} object that represents the access conditions for the source.
* @param destinationAccessCondition
* An {@link AccessCondition} object that represents the access conditions for the destination.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request.
* Specifying 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 String
which represents the copy ID associated with the copy operation.
*
* @throws StorageException
* If a storage service error occurred.
*
*/
@DoesServiceRequest
public final String startCopy(final URI source, final AccessCondition sourceAccessCondition,
final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext)
throws StorageException {
if (opContext == null) {
opContext = new OperationContext();
}
opContext.initialize();
options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);
return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
this.startCopyImpl(source, sourceAccessCondition, destinationAccessCondition, options),
options.getRetryPolicyFactory(), opContext);
}
private StorageRequest startCopyImpl(
final URI source, final AccessCondition sourceAccessCondition,
final AccessCondition destinationAccessCondition, final BlobRequestOptions options) {
final StorageRequest putRequest =
new StorageRequest(options, this.getStorageUri()) {
@Override
public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
throws Exception {
// toASCIIString() must be used in order to appropriately encode the URI
return BlobRequest.copyFrom(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
options, context, sourceAccessCondition, destinationAccessCondition, source.toASCIIString(),
blob.snapshotID);
}
@Override
public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationContext context) {
BlobRequest.addMetadata(connection, blob.metadata, context);
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, 0, context);
}
@Override
public String preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) {
this.setNonExceptionedRetryableFailure(true);
return null;
}
blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
blob.properties.setCopyState(BlobResponse.getCopyState(this.getConnection()));
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.populateAndApplyDefaults(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) {
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, context, accessCondition);
}
@Override
public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationContext context) {
if (metadata != null) {
BlobRequest.addMetadata(connection, metadata, context);
}
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
}
@Override
public CloudBlob preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) {
this.setNonExceptionedRetryableFailure(true);
return null;
}
CloudBlob snapshot = null;
final String snapshotTime = BlobResponse.getSnapshotTime(this.getConnection());
if (blob instanceof CloudBlockBlob) {
snapshot = new CloudBlockBlob(blob.getName(), snapshotTime, CloudBlob.this.getContainer());
}
else if (blob instanceof CloudPageBlob) {
snapshot = new CloudPageBlob(blob.getName(), snapshotTime, CloudBlob.this.getContainer());
}
else if (blob instanceof CloudAppendBlob) {
snapshot = new CloudAppendBlob(blob.getName(), snapshotTime, CloudBlob.this.getContainer());
}
snapshot.setProperties(blob.properties);
// use the specified metadata if not null : otherwise blob's metadata
snapshot.setMetadata(metadata != null ? metadata : blob.metadata);
snapshot.updateEtagAndLastModifiedFromResponse(this.getConnection());
return snapshot;
}
};
return putRequest;
}
/**
* Deletes the blob.
*
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final void delete() throws StorageException {
this.delete(DeleteSnapshotsOption.NONE, null /* accessCondition */, null /* options */, null /* opContext */);
}
/**
* Deletes the blob using the specified snapshot and request options, and operation context.
*
* 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 snapshots, or the blob
* and its snapshots.
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);
ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
this.deleteImpl(deleteSnapshotsOption, accessCondition, options), options.getRetryPolicyFactory(),
opContext);
}
/**
* Deletes the blob if it exists.
*
* 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 snapshots, or the blob
* and its snapshots.
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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 {
options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);
boolean exists = this.exists(true, accessCondition, options, opContext);
if (exists) {
try {
this.delete(deleteSnapshotsOption, accessCondition, options, opContext);
return true;
}
catch (StorageException e) {
if (e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND
&& StorageErrorCodeStrings.BLOB_NOT_FOUND.equals(e.getErrorCode())) {
return false;
}
else {
throw e;
}
}
}
else {
return false;
}
}
private StorageRequest deleteImpl(
final DeleteSnapshotsOption deleteSnapshotsOption, final AccessCondition accessCondition,
final BlobRequestOptions options) {
final StorageRequest deleteRequest = new StorageRequest(
options, this.getStorageUri()) {
@Override
public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
throws Exception {
return BlobRequest.deleteBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
options, context, accessCondition, blob.snapshotID, deleteSnapshotsOption);
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context);
}
@Override
public Void preProcessResponse(CloudBlob parentObject, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) {
this.setNonExceptionedRetryableFailure(true);
return null;
}
return null;
}
};
return deleteRequest;
}
/**
* Downloads the contents of a blob to a stream.
*
* @param outStream
* An {@link OutputStream}
object that represents the target stream.
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final void download(final OutputStream outStream) throws StorageException {
this.download(outStream, null /* accessCondition */, null /* options */, null /* opContext */);
}
/**
* Downloads the contents of a blob to a stream using the specified request options and operation context.
*
* @param outStream
* An 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 StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final void download(final OutputStream outStream, final AccessCondition accessCondition,
BlobRequestOptions options, OperationContext opContext) throws StorageException {
if (opContext == null) {
opContext = new OperationContext();
}
opContext.initialize();
options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);
ExecutionEngine.executeWithRetry(this.blobServiceClient, this, this.downloadToStreamImpl(
null /* blobOffset */, null /* length */, outStream, accessCondition, options, opContext), options
.getRetryPolicyFactory(), opContext);
}
/**
* Downloads the contents of a blob to a stream.
*
* @param offset
* A long
which represents the offset to use as the starting point for the source.
* @param length
* A Long
which represents the number of bytes to read or null
.
* @param outStream
* An {@link OutputStream}
object that represents the target stream.
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final void downloadRange(final long offset, final Long length, final OutputStream outStream)
throws StorageException {
this.downloadRange(offset, length, outStream, null /* accessCondition */, null /* options */, null /* opContext */);
}
/**
* Downloads the contents of a blob to a stream using the specified request options and operation context.
*
* @param offset
* A long
which represents the offset to use as the starting point for the source.
* @param length
* A Long
which represents the number of bytes to read or null
.
* @param outStream
* An {@link 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 StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final void downloadRange(final long offset, final Long length, final OutputStream outStream,
final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
throws StorageException {
if (offset < 0 || (length != null && length <= 0)) {
throw new IndexOutOfBoundsException();
}
if (opContext == null) {
opContext = new OperationContext();
}
opContext.initialize();
options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);
if (options.getUseTransactionalContentMD5() && (length != null && length > 4 * Constants.MB)) {
throw new IllegalArgumentException(SR.INVALID_RANGE_CONTENT_MD5_HEADER);
}
ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
this.downloadToStreamImpl(offset, length, outStream, accessCondition, options, opContext),
options.getRetryPolicyFactory(), opContext);
}
/**
* Populates a blob's properties and metadata.
*
* This method populates the blob's system properties and user-defined metadata. Before reading or modifying a
* blob's properties or metadata, call this method or its overload to retrieve the latest values for the blob's
* properties and metadata from the Microsoft Azure storage service.
*
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final void downloadAttributes() throws StorageException {
this.downloadAttributes(null /* accessCondition */, null /* options */, null /* opContext */);
}
/**
* Populates a blob's properties and metadata using the specified request options and operation context.
*
* This method populates the blob's system properties and user-defined metadata. Before reading or modifying a
* blob's properties or metadata, call this method or its overload to retrieve the latest values for the blob's
* properties and metadata from the Microsoft Azure storage service.
*
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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.populateAndApplyDefaults(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) {
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.getBlobProperties(
blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context,
accessCondition, blob.snapshotID);
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context);
}
@Override
public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
this.setNonExceptionedRetryableFailure(true);
return null;
}
// Set attributes
final BlobAttributes retrievedAttributes = BlobResponse.getBlobAttributes(this.getConnection(),
blob.getStorageUri(), blob.snapshotID);
if (retrievedAttributes.getProperties().getBlobType() != blob.properties.getBlobType()) {
throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE, String.format(
SR.INVALID_BLOB_TYPE, blob.properties.getBlobType(), retrievedAttributes.getProperties()
.getBlobType()), Constants.HeaderConstants.HTTP_UNUSED_306, null, null);
}
blob.properties = retrievedAttributes.getProperties();
blob.metadata = retrievedAttributes.getMetadata();
return null;
}
};
return getRequest;
}
@DoesServiceRequest
private final StorageRequest downloadToStreamImpl(Long offset,
Long length, final OutputStream userStream, final AccessCondition accessCondition,
final BlobRequestOptions options, OperationContext opContext) {
options.assertPolicyIfRequired();
final Long userSpecifiedLength = length;
final long startingOffset = offset == null ? 0 : offset;
final boolean isRangeGet = offset != null;
int discardFirst = 0;
Long endOffset = null;
boolean bufferIV = false;
if (isRangeGet && options.getEncryptionPolicy() != null)
{
// Let's say the user requests a download with offset = 39 and length = 54
// First calculate the endOffset if length has value.
// endOffset starts at 92 (39 + 54 - 1), but then gets increased to 95 (one less than the next higher multiple of 16)
if (length != null)
{
endOffset = offset + length - 1;
// AES-CBC works in 16 byte blocks. So if a user specifies a range whose start and end offsets are not multiples of 16,
// update them so we can download entire AES blocks to decrypt.
// Adjust the end offset to be a multiple of 15 mod 16.
endOffset += 15 - endOffset % 16;
}
// Adjust the offset to be a multiple of 16.
// offset gets reduced down to the highest multiple of 16 lower then the current value (32)
discardFirst = (int)(offset % 16);
offset -= discardFirst;
// We need another 16 bytes for IV if offset is not 0. If the offset is 0, it is the first AES block
// and the IV is obtained from blob metadata.
// offset is reduced by another 16 (to a final value of 16)
if (offset > 15)
{
offset -= 16;
bufferIV = true;
}
// Adjust the length according to the new start and end offsets.
// length = 80 (a multiple of 16)
if (endOffset != null)
{
length = endOffset - offset + 1;
}
}
final Long offsetFinal = offset;
final Long lengthFinal = length;
final int discardFirstFinal = discardFirst;
final Long endOffsetFinal = endOffset;
final boolean bufferIVFinal = bufferIV;
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.getOffset() == null) {
this.setOffset(offsetFinal);
}
if (this.getLength() == null) {
this.setLength(lengthFinal);
}
AccessCondition tempCondition = (this.getETagLockCondition() != null) ? this.getETagLockCondition()
: accessCondition;
return BlobRequest.getBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
options, context, tempCondition, blob.snapshotID, this.getOffset(), this.getLength(),
(options.getUseTransactionalContentMD5() && !this.getArePropertiesPopulated()));
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context);
}
@Override
public Integer preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_PARTIAL
&& this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
this.setNonExceptionedRetryableFailure(true);
return null;
}
if (!this.getArePropertiesPopulated()) {
String originalContentMD5 = null;
final BlobAttributes retrievedAttributes = BlobResponse.getBlobAttributes(this.getConnection(),
blob.getStorageUri(), blob.snapshotID);
// 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();
this.setContentMD5(retrievedAttributes.getProperties().getContentMD5());
blob.properties.setContentMD5(originalContentMD5);
this.setLockedETag(blob.properties.getEtag());
this.setArePropertiesPopulated(true);
}
// If the download fails and Get Blob needs to resume the download, going to the
// same storage location is important to prevent a possible ETag mismatch.
this.setRequestLocationMode(this.getResult().getTargetLocation() == StorageLocation.PRIMARY ? RequestLocationMode.PRIMARY_ONLY
: RequestLocationMode.SECONDARY_ONLY);
return null;
}
@Override
public Integer postProcessResponse(HttpURLConnection connection, CloudBlob blob, CloudBlobClient client,
OperationContext context, Integer storageObject) throws Exception {
final Boolean validateMD5 = !options.getDisableContentMD5Validation()
&& !Utility.isNullOrEmpty(this.getContentMD5());
final String contentLength = connection.getHeaderField(Constants.HeaderConstants.CONTENT_LENGTH);
final long expectedLength = Long.parseLong(contentLength);
Logger.info(context, String.format(SR.CREATING_NETWORK_STREAM, expectedLength));
final NetworkInputStream streamRef = new NetworkInputStream(connection.getInputStream(), expectedLength);
OutputStream outStream = userStream;
try {
if (options.getEncryptionPolicy() != null)
{
outStream = BlobEncryptionPolicy.wrapUserStreamWithDecryptStream(blob, userStream, options,
blob.metadata, blob.properties.getLength(), isRangeGet, endOffsetFinal,
userSpecifiedLength, discardFirstFinal, bufferIVFinal);
}
// writeToOutputStream will update the currentRequestByteCount on this request in case a retry
// is needed and download should resume from that point
final StreamMd5AndLength descriptor = Utility.writeToOutputStream(streamRef, outStream, -1, false,
validateMD5, context, options, true, this);
// length was already checked by the NetworkInputStream, now check Md5
if (validateMD5 && !this.getContentMD5().equals(descriptor.getMd5())) {
throw new StorageException(StorageErrorCodeStrings.INVALID_MD5, String.format(
SR.BLOB_HASH_MISMATCH, this.getContentMD5(), descriptor.getMd5()),
Constants.HeaderConstants.HTTP_UNUSED_306, null, null);
}
}
finally {
// Close the stream and return. Closing an already closed stream is harmless. So its fine to try
// to drain the response and close the stream again in the executor.
streamRef.close();
if (options.getEncryptionPolicy() != null) {
outStream.close();
}
}
return null;
}
@Override
public void recoveryAction(OperationContext context) throws IOException {
if (this.getETagLockCondition() == null && (!Utility.isNullOrEmpty(this.getLockedETag()))) {
AccessCondition etagLockCondition = new AccessCondition();
etagLockCondition.setIfMatch(this.getLockedETag());
if (accessCondition != null) {
etagLockCondition.setLeaseID(accessCondition.getLeaseID());
}
this.setETagLockCondition(etagLockCondition);
}
if (this.getCurrentRequestByteCount() > 0) {
this.setOffset(startingOffset + this.getCurrentRequestByteCount());
if (lengthFinal != null) {
this.setLength(lengthFinal - this.getCurrentRequestByteCount());
}
}
}
};
return getRequest;
}
/**
* Downloads a range of bytes from the blob to the given byte buffer.
*
* @param blobOffset
* A long
which represents the offset within the blob to begin downloading.
* @param length
* A Long
which represents the number of bytes to read.
* @param buffer
* A byte
array which represents the buffer to write to.
* @param bufferOffset
* An int
which represents the offset in the byte buffer to begin writing.
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request.
* @param opContext
* An {@link OperationContext} object used to track the execution of the operation.
* @returns The total number of bytes read into the buffer.
*
* @throws StorageException
* an exception representing any error which occurred during the operation.
*/
@DoesServiceRequest
protected final int downloadRangeInternal(final long blobOffset, final Long length, final byte[] buffer,
final int bufferOffset, final AccessCondition accessCondition, BlobRequestOptions options,
OperationContext opContext) throws StorageException {
if (bufferOffset < 0 || blobOffset < 0 || (length != null && length <= 0)) {
throw new IndexOutOfBoundsException();
}
if (opContext == null) {
opContext = new OperationContext();
}
options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);
if (options.getUseTransactionalContentMD5() && (length != null && length > 4 * Constants.MB)) {
throw new IllegalArgumentException(SR.INVALID_RANGE_CONTENT_MD5_HEADER);
}
WrappedByteArrayOutputStream outputStream = new WrappedByteArrayOutputStream(buffer, bufferOffset);
ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
this.downloadToStreamImpl(blobOffset, length, outputStream, accessCondition, options, opContext),
options.getRetryPolicyFactory(), opContext);
return outputStream.getPosition();
}
/**
* Downloads a range of bytes from the blob to the given byte buffer.
*
* @param offset
* A long
which represents the byte offset to use as the starting point for the source.
* @param length
* A Long
which represents the number of bytes to read or null.
* @param buffer
* A byte
array which represents the buffer to which the blob bytes are downloaded.
* @param bufferOffset
* An int
which represents the byte offset to use as the starting point for the target.
* @returns The total number of bytes read into the buffer.
*
* @throws StorageException
*/
@DoesServiceRequest
public final int downloadRangeToByteArray(final long offset, final Long length, final byte[] buffer,
final int bufferOffset) throws StorageException {
return this.downloadRangeToByteArray(offset, length, buffer, bufferOffset, null /* accessCondition */,
null /* options */, null /* opContext */);
}
/**
* Downloads a range of bytes from the blob to the given byte buffer, using the specified request options and
* operation context.
*
* @param offset
* A long
which represents the byte offset to use as the starting point for the source.
* @param length
* A Long
which represents the number of bytes to read or null
.
* @param buffer
* A byte
array which represents the buffer to which the blob bytes are downloaded.
* @param bufferOffset
* An int
which represents the byte offset to use as the starting point for the target.
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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.
* @returns The total number of bytes read into the buffer.
*
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final int downloadRangeToByteArray(final long offset, final Long length, final byte[] buffer,
final int bufferOffset, final AccessCondition accessCondition, BlobRequestOptions options,
OperationContext opContext) throws StorageException {
Utility.assertNotNull("buffer", buffer);
if (length != null) {
if (length + bufferOffset > buffer.length) {
throw new IndexOutOfBoundsException();
}
}
if (opContext == null) {
opContext = new OperationContext();
}
opContext.initialize();
return this.downloadRangeInternal(offset, length, buffer, bufferOffset, accessCondition, options, opContext);
}
/**
* Downloads a range of bytes from the blob to the given byte buffer.
*
* @param buffer
* A byte
array which represents the buffer to which the blob bytes are downloaded.
* @param bufferOffset
* An int
which represents the byte offset to use as the starting point for the target.
*
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final int downloadToByteArray(final byte[] buffer, final int bufferOffset) throws StorageException {
return this
.downloadToByteArray(buffer, bufferOffset, null /* accessCondition */, null /* options */, null /* opContext */);
}
/**
* Downloads a range of bytes from the blob to the given byte buffer, using the specified request options and
* operation context.
*
* @param buffer
* A byte
array which represents the buffer to which the blob bytes are downloaded.
* @param bufferOffset
* A long
which represents the byte offset to use as the starting point for the target.
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);
WrappedByteArrayOutputStream outputStream = new WrappedByteArrayOutputStream(buffer, bufferOffset);
ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
this.downloadToStreamImpl(null, null, outputStream, accessCondition, options, opContext),
options.getRetryPolicyFactory(), opContext);
return outputStream.getPosition();
}
/**
* Uploads a blob from data in a byte array. If the blob already exists on the service, it will be overwritten.
*
* @param buffer
* A byte
array which represents the data to write to the blob.
* @param offset
* A int
which represents the offset of the byte array from which to start the data upload.
* @param length
* An int
which represents the number of bytes to upload from the input buffer.
*
* @throws StorageException
* If a storage service error occurred.
* @throws IOException
*/
public void uploadFromByteArray(final byte[] buffer, final int offset, final int length) throws StorageException,
IOException {
uploadFromByteArray(buffer, offset, length, null /* accessCondition */, null /* options */, null /* opContext */);
}
/**
* Uploads a blob from data in a byte array. If the blob already exists on the service, it will be overwritten.
*
* @param buffer
* A byte
array which represents the data to write to the blob.
* @param offset
* A int
which represents the offset of the byte array from which to start the data upload.
* @param length
* An int
which represents the number of bytes to upload from the input buffer.
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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);
inputStream.close();
}
/**
* Uploads a blob from a file. If the blob already exists on the service, it will be overwritten.
*
* @param path
* A String
which represents the path to the file to be uploaded.
*
* @throws StorageException
* If a storage service error occurred.
* @throws IOException
*/
public void uploadFromFile(final String path) throws StorageException, IOException {
uploadFromFile(path, null /* accessCondition */, null /* options */, null /* opContext */);
}
/**
* Uploads a blob from a file. If the blob already exists on the service, it will be overwritten.
*
* @param path
* A String
which represents the path to the file to be uploaded.
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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);
inputStream.close();
}
/**
* Downloads a blob, storing the contents in a file.
*
* @param path
* A String
which represents the path to the file that will be created with the contents of
* the blob.
*
* @throws StorageException
* If a storage service error occurred.
* @throws IOException
*/
public void downloadToFile(final String path) throws StorageException, IOException {
downloadToFile(path, null /* accessCondition */, null /* options */, null /* opContext */);
}
/**
* Downloads a blob, storing the contents in a file.
*
* @param path
* A String
which represents the path to the file that will be created with the contents of
* the blob.
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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));
try {
this.download(outputStream, accessCondition, options, opContext);
outputStream.close();
}
catch (StorageException e) {
deleteEmptyFileOnException(outputStream, path);
throw e;
}
catch (IOException e) {
deleteEmptyFileOnException(outputStream, path);
throw e;
}
}
/**
* Helper to delete an empty file in the case of an exception
*
* @param outputStream
* @param path
* @throws IOException
*/
private void deleteEmptyFileOnException(OutputStream outputStream, String path) {
try {
outputStream.close();
File fileToDelete = new File(path);
fileToDelete.delete();
}
catch (Exception e) {
// Best effort delete.
}
}
/**
* Checks to see if the blob exists.
*
* @return true
if the blob exists, otherwise 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
* If a storage service error occurred.
*/
@DoesServiceRequest
public final boolean exists(final AccessCondition accessCondition, BlobRequestOptions options,
OperationContext opContext) throws StorageException {
return this.exists(false /* primaryOnly */, accessCondition, options, opContext);
}
@DoesServiceRequest
private final boolean exists(final boolean primaryOnly, final AccessCondition accessCondition,
BlobRequestOptions options, OperationContext opContext) throws StorageException {
if (opContext == null) {
opContext = new OperationContext();
}
opContext.initialize();
options = BlobRequestOptions.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient);
return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
this.existsImpl(primaryOnly, accessCondition, options), options.getRetryPolicyFactory(), opContext);
}
private StorageRequest existsImpl(final boolean primaryOnly,
final AccessCondition accessCondition, final BlobRequestOptions options) {
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.getBlobProperties(
blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context,
accessCondition, blob.snapshotID);
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context);
}
@Override
public Boolean preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) {
final BlobAttributes retrievedAttributes = BlobResponse.getBlobAttributes(this.getConnection(),
blob.getStorageUri(), blob.snapshotID);
blob.properties = retrievedAttributes.getProperties();
blob.metadata = retrievedAttributes.getMetadata();
return Boolean.valueOf(true);
}
else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return Boolean.valueOf(false);
}
else {
this.setNonExceptionedRetryableFailure(true);
// return false instead of null to avoid SCA issues
return false;
}
}
};
return getRequest;
}
/**
* Returns a shared access signature for the blob using the specified group policy identifier and operation context.
* Note this does not contain the leading "?".
*
* @param policy
* A 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
* A {@link SharedAccessBlobHeaders}
object that represents 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 {
return this.generateSharedAccessSignature(policy, headers, groupPolicyIdentifier,
null /* IP range */, null /* protocols */);
}
/**
* Returns a shared access signature for the blob using the specified group policy identifier and operation context.
* Note this does not contain the leading "?".
*
* @param policy
* A {@link SharedAccessPolicy}
object that represents the access policy for the shared
* access signature.
* @param headers
* A {@link SharedAccessBlobHeaders}
object that represents 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.
* @param ipRange
* A {@link IPRange} object containing the range of allowed IP addresses.
* @param protocols
* A {@link SharedAccessProtocols} representing the allowed Internet protocols.
*
* @return A 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, final IPRange ipRange, final SharedAccessProtocols protocols)
throws InvalidKeyException, StorageException {
if (!StorageCredentialsHelper.canCredentialsSignRequest(this.blobServiceClient.getCredentials())) {
throw new IllegalArgumentException(SR.CANNOT_CREATE_SAS_WITHOUT_ACCOUNT_KEY);
}
if (this.isSnapshot()) {
throw new IllegalArgumentException(SR.CANNOT_CREATE_SAS_FOR_SNAPSHOTS);
}
final String resourceName = this.getCanonicalName(true);
final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHashForBlobAndFile(
policy, headers, groupPolicyIdentifier, resourceName, ipRange, protocols, this.blobServiceClient);
final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignatureForBlobAndFile(
policy, headers, groupPolicyIdentifier, "b", ipRange, protocols, signature);
return builder.toString();
}
/**
* Returns the canonical name of the blob in the format of
* /<service-name>/<account-name>/<container-name>/<blob-name>.
*
* This format is used for Shared Access operations.
*
* @param ignoreSnapshotTime
* true
if the snapshot time is ignored; otherwise, false
.
*
* @return The canonical name in the format of /<service-name>/<account-name>
* /<container-name>/<blob-name>.
*/
String getCanonicalName(final boolean ignoreSnapshotTime) {
StringBuilder canonicalName = new StringBuilder("/");
canonicalName.append(SR.BLOB);
if (this.blobServiceClient.isUsePathStyleUris()) {
canonicalName.append(this.getUri().getRawPath());
}
else {
canonicalName.append(PathUtility.getCanonicalPathFromCredentials(
this.blobServiceClient.getCredentials(), this.getUri().getRawPath()));
}
if (!ignoreSnapshotTime && this.snapshotID != null) {
canonicalName.append("?snapshot=");
canonicalName.append(this.snapshotID);
}
return canonicalName.toString();
}
/**
* Returns the blob's container.
*
* @return A {@link CloudBlobContainer} object that represents the container of the blob.
* @throws StorageException
* If a storage service error occurred.
* @throws URISyntaxException
* If the resource URI is invalid.
*/
@Override
public final CloudBlobContainer getContainer() throws StorageException, URISyntaxException {
if (this.container == null) {
final StorageUri containerURI = PathUtility.getContainerURI(this.getStorageUri(),
this.blobServiceClient.isUsePathStyleUris());
this.container = new CloudBlobContainer(containerURI, this.blobServiceClient.getCredentials());
}
return this.container;
}
/**
* Returns the metadata for the blob.
*
* @return A 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.
*
*/
public final String getName() {
return this.name;
}
/**
* Returns the blob item's parent.
*
* @return A {@link CloudBlobDirectory} object that represents the parent directory for the blob.
*
* @throws StorageException
* If a storage service error occurred.
* @throws URISyntaxException
* If the resource URI is invalid.
*/
@Override
public final CloudBlobDirectory getParent() throws URISyntaxException, StorageException {
if (this.parent == null) {
final String parentName = getParentNameFromURI(this.getStorageUri(),
this.blobServiceClient.getDirectoryDelimiter(), this.getContainer());
if (parentName != null) {
StorageUri parentURI = PathUtility.appendPathToUri(this.container.getStorageUri(), parentName);
this.parent = new CloudBlobDirectory(parentURI, parentName, this.blobServiceClient, this.getContainer());
}
}
return this.parent;
}
/**
* Returns the blob's properties.
*
* @return A {@link BlobProperties} object that represents the properties of the blob.
*/
public final BlobProperties getProperties() {
return this.properties;
}
/**
* Returns the blob's copy state.
*
* @return A {@link CopyState} object that represents the copy state of the blob.
*/
public CopyState getCopyState() {
return this.properties.getCopyState();
}
/**
* Returns the snapshot or shared access signature qualified URI for this blob.
*
* @return A {@link StorageUri} object that represents the snapshot or shared access signature.
*
* @throws StorageException
* If a storage service error occurred.
* @throws URISyntaxException
* If the resource URI is invalid.
*/
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 A String
which represents the Blob Snapshot ID.
*/
public final String getSnapshotID() {
return this.snapshotID;
}
/**
* Returns the list of URIs for all locations.
*
* @return A {@link StorageUri} that represents the list of URIs for all locations..
*/
@Override
public final StorageUri getStorageUri() {
return this.storageUri;
}
/**
* Gets the number of bytes to buffer when writing to a {@link BlobOutputStream} (block and page blobs).
*
* @return
* A int
which represents the number of bytes to buffer or the size of a block, in bytes.
*/
public final int getStreamWriteSizeInBytes() {
return this.streamWriteSizeInBytes;
}
/**
* Returns the minimum read size when using a {@link BlobInputStream}.
*
* @return A int
which represents the minimum read size, in bytes, when using a {@link BlobInputStream}
* object.
*/
public final int getStreamMinimumReadSizeInBytes() {
return this.streamMinimumReadSizeInBytes;
}
/**
* Returns the transformed URI for the resource if the given credentials require transformation.
*
* @param opContext
* An {@link OperationContext} object that represents the context for the current operation. This object
* is used to track requests to the storage service, and to provide additional runtime information about
* the operation.
*
* @return A {@link StorageUri} object that represents the transformed URI.
*
* @throws IllegalArgumentException
* If the URI is not absolute.
* @throws StorageException
* If a storage service error occurred.
* @throws URISyntaxException
* If the resource URI is invalid.
*/
protected final StorageUri getTransformedAddress(final OperationContext opContext) throws URISyntaxException,
StorageException {
return this.blobServiceClient.getCredentials().transformUri(this.getStorageUri(), opContext);
}
/**
* Returns the URI for this blob.
*
* @return A 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 #setStreamMinimumReadSizeInBytes(int)} 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 #setStreamMinimumReadSizeInBytes(int)} to configure the read size.
*
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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.populateAndApplyDefaults(options, this.properties.getBlobType(), this.blobServiceClient,
false /* setStartTime */);
return new BlobInputStream(this, accessCondition, options, opContext);
}
/**
* Verifies the passed in URI. Then parses it and uses its components to populate this resource's properties.
*
* @param completeUri
* A {@link StorageUri} object which represents the complete URI.
* @param credentials
* A {@link StorageCredentials} object used to authenticate access.
* @throws StorageException
* If a storage service error occurred.
*/
private void parseQueryAndVerify(final StorageUri completeUri, final StorageCredentials credentials)
throws StorageException {
Utility.assertNotNull("completeUri", completeUri);
if (!completeUri.isAbsolute()) {
throw new IllegalArgumentException(String.format(SR.RELATIVE_ADDRESS_NOT_PERMITTED, completeUri.toString()));
}
this.storageUri = PathUtility.stripURIQueryAndFragment(completeUri);
final HashMap queryParameters = PathUtility.parseQueryString(completeUri.getQuery());
final String[] snapshotIDs = queryParameters.get(BlobConstants.SNAPSHOT);
if (snapshotIDs != null && snapshotIDs.length > 0) {
this.snapshotID = snapshotIDs[0];
}
final StorageCredentialsSharedAccessSignature parsedCredentials =
SharedAccessSignatureHelper.parseQuery(queryParameters);
if (credentials != null && parsedCredentials != null) {
throw new IllegalArgumentException(SR.MULTIPLE_CREDENTIALS_PROVIDED);
}
try {
final boolean usePathStyleUris = Utility.determinePathStyleFromUri(this.storageUri.getPrimaryUri());
this.blobServiceClient = new CloudBlobClient(PathUtility.getServiceClientBaseAddress(
this.getStorageUri(), usePathStyleUris), credentials != null ? credentials : parsedCredentials);
this.name = PathUtility.getBlobNameFromURI(this.storageUri.getPrimaryUri(), usePathStyleUris);
}
catch (final URISyntaxException e) {
throw Utility.generateNewUnexpectedStorageException(e);
}
}
/**
* Releases the lease on the blob.
*
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is
* required to be set on the AccessCondition.
*
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final void releaseLease(final AccessCondition accessCondition) throws StorageException {
this.releaseLease(accessCondition, null /* options */, null /* opContext */);
}
/**
* Releases the lease on the blob using the specified request options and operation context.
*
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.The LeaseID is
* required to be set on the AccessCondition.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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.populateAndApplyDefaults(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) {
final StorageRequest putRequest = new StorageRequest(
options, this.getStorageUri()) {
@Override
public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
throws Exception {
return BlobRequest.leaseBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
options, context, accessCondition, LeaseAction.RELEASE, null /* leaseTimeInSeconds */,
null /* proposedLeaseId */, null /*breakPeriodInSeconds */);
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
}
@Override
public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
this.setNonExceptionedRetryableFailure(true);
return null;
}
updateEtagAndLastModifiedFromResponse(this.getConnection());
blob.properties.setLeaseStatus(LeaseStatus.UNLOCKED);
return null;
}
};
return putRequest;
}
/**
* Renews an existing lease.
*
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is
* required to be set on the AccessCondition.
*
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final void renewLease(final AccessCondition accessCondition) throws StorageException {
this.renewLease(accessCondition, null /* options */, null /* opContext */);
}
/**
* Renews an existing lease using the specified request options and operation context.
*
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is
* required to be set on the AccessCondition.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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.populateAndApplyDefaults(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) {
final StorageRequest putRequest = new StorageRequest(
options, this.getStorageUri()) {
@Override
public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
throws Exception {
return BlobRequest.leaseBlob(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()),
options, context, accessCondition, LeaseAction.RENEW, null /* leaseTimeInSeconds */,
null /* proposedLeaseId */, null /*breakPeriodInSeconds */);
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
}
@Override
public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
this.setNonExceptionedRetryableFailure(true);
return null;
}
updateEtagAndLastModifiedFromResponse(this.getConnection());
return null;
}
};
return putRequest;
}
/**
* 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 {@link StorageUri} that represents the list of URIs for all locations.
*/
protected void setStorageUri(final StorageUri storageUri) {
this.storageUri = storageUri;
}
/**
* Sets the number of bytes to buffer when writing to a {@link BlobOutputStream} (block and page blobs).
*
* @param streamWriteSizeInBytes
* An int
that represents the number of bytes to buffer or the size of a block, in bytes.
*/
public abstract void setStreamWriteSizeInBytes(int streamWriteSizeInBytes);
/**
* Sets the minimum read size when using a {@link BlobInputStream}.
*
* @param minimumReadSize
* An int
that represents the minimum block size, in bytes, for reading from a blob while
* using a {@link BlobInputStream} object. Must be greater than or equal to 16 KB.
* @throws IllegalArgumentException
* If 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. If the blob already exists on the service, it will be overwritten.
*
* @param sourceStream
* An InputStream
object that represents the source stream to upload.
*
* @param length
* An long
that represents the length of the stream data in bytes, or -1 if unknown. The
* length must be greater than zero and a
* multiple of 512 for page blobs.
*
* @throws IOException
* If an I/O exception occurred.
*
* @throws StorageException
* If a storage service error occurred.
*
*/
@DoesServiceRequest
public abstract void upload(InputStream sourceStream, long length) throws StorageException, IOException;
/**
* Uploads the source stream data to the blob using the specified lease ID, request options, and operation context.
* If the blob already exists on the service, it will be overwritten.
*
* @param sourceStream
* An 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 the blob's metadata to the storage service.
*
* Use {@link CloudBlob#downloadAttributes} to retrieve the latest values for the blob's properties and metadata
* from the Microsoft Azure storage service.
*
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final void uploadMetadata() throws StorageException {
this.uploadMetadata(null /* accessCondition */, null /* options */, null /* opContext */);
}
/**
* Uploads the blob's metadata to the storage service using the specified lease ID, request options, and operation
* context.
*
* Use {@link CloudBlob#downloadAttributes} to retrieve the latest values for the blob's properties and metadata
* from the Microsoft Azure storage service.
*
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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.populateAndApplyDefaults(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) {
final StorageRequest putRequest = new StorageRequest(
options, this.getStorageUri()) {
@Override
public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
throws Exception {
return BlobRequest.setBlobMetadata(blob.getTransformedAddress(context)
.getUri(this.getCurrentLocation()), options, context, accessCondition);
}
@Override
public void setHeaders(HttpURLConnection connection, CloudBlob blob, OperationContext context) {
BlobRequest.addMetadata(connection, blob.metadata, context);
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
}
@Override
public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
this.setNonExceptionedRetryableFailure(true);
return null;
}
blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
this.getResult().setRequestServiceEncrypted(CloudBlob.isServerRequestEncrypted(this.getConnection()));
return null;
}
};
return putRequest;
}
/**
* Updates the blob's properties to the storage service.
*
* Use {@link CloudBlob#downloadAttributes} to retrieve the latest values for the blob's properties and metadata
* from the Microsoft Azure storage service.
*
* @throws StorageException
* If a storage service error occurred.
*/
@DoesServiceRequest
public final void uploadProperties() throws StorageException {
this.uploadProperties(null /* accessCondition */, null /* options */, null /*opContext */);
}
/**
* Updates the blob's properties using the specified lease ID, request options, and operation context.
*
* Use {@link CloudBlob#downloadAttributes} to retrieve the latest values for the blob's properties and metadata
* from the Microsoft Azure storage service.
*
* @param accessCondition
* An {@link AccessCondition} object that represents the access conditions for the blob.
* @param options
* A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
* 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.populateAndApplyDefaults(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) {
final StorageRequest putRequest = new StorageRequest(
options, this.getStorageUri()) {
@Override
public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context)
throws Exception {
return BlobRequest.setBlobProperties(
blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context,
accessCondition, blob.properties);
}
@Override
public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
throws Exception {
StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context);
}
@Override
public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context)
throws Exception {
if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
this.setNonExceptionedRetryableFailure(true);
return null;
}
blob.updateEtagAndLastModifiedFromResponse(this.getConnection());
return null;
}
};
return putRequest;
}
/**
* Retrieves the parent name for a blob URI.
*
* @param resourceAddress
* A {@link StorageUri} object which represents the resource URI.
* @param delimiter
* A String
which specifies the directory delimiter to use.
* @param usePathStyleUris
* A {@link CloudBlobContainer} object which represents the blob container.
*
* @return A String
which represents the parent address for a blob URI.
*
* @throws URISyntaxException
*/
protected static String getParentNameFromURI(final StorageUri resourceAddress, final String delimiter,
final CloudBlobContainer container) throws URISyntaxException {
Utility.assertNotNull("resourceAddress", resourceAddress);
Utility.assertNotNull("container", container);
Utility.assertNotNullOrEmpty("delimiter", delimiter);
String containerName = container.getName() + "/";
String relativeURIString = Utility.safeRelativize(container.getStorageUri().getPrimaryUri(),
resourceAddress.getPrimaryUri());
if (relativeURIString.endsWith(delimiter)) {
relativeURIString = relativeURIString.substring(0, relativeURIString.length() - delimiter.length());
}
String parentName;
if (Utility.isNullOrEmpty(relativeURIString)) {
// Case 1 /[Delimiter]*? => /
// Parent of container is container itself
parentName = null;
}
else {
final int lastDelimiterDex = relativeURIString.lastIndexOf(delimiter);
if (lastDelimiterDex < 0) {
// Case 2 //
// Parent of a folder is container
parentName = "";
}
else {
// Case 3 ///[/]*
// Parent of blob is folder
parentName = relativeURIString.substring(0, lastDelimiterDex + delimiter.length());
if (parentName != null && parentName.equals(containerName)) {
parentName = "";
}
}
}
return parentName;
}
protected static boolean isServerRequestEncrypted(HttpURLConnection connection) {
return Constants.TRUE.equals(connection.getHeaderField(Constants.HeaderConstants.SERVER_REQUEST_ENCRYPTED));
}
}