diff --git a/ChangeLog.txt b/ChangeLog.txt
index 4606838131e9a..9ad2d736cdc6c 100644
--- a/ChangeLog.txt
+++ b/ChangeLog.txt
@@ -1,3 +1,6 @@
+2016.x.x Version 4.3.0
+ * Added support for getBlobReferenceFromServer methods on CloudBlobContainer to support retrieving a blob without knowing its type.
+
2016.04.07 Version 4.2.0
* Added support for setting a library-wide proxy. The default proxy can be set on OperationContext.
* Added support in Page Blob for getting a list of differing page ranges between snapshot versions.
diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobTestHelper.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobTestHelper.java
index aeb5fd28ec903..0a2334cf04a6d 100644
--- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobTestHelper.java
+++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/BlobTestHelper.java
@@ -384,6 +384,7 @@ public static void assertAreEqual(CloudBlob blob1, CloudBlob blob2) throws URISy
}
else {
assertNotNull(blob2);
+ assertEquals(blob1.getClass(), blob2.getClass());
assertEquals(blob1.getUri(), blob2.getUri());
assertEquals(blob1.getSnapshotID(), blob2.getSnapshotID());
assertEquals(blob1.isSnapshot(), blob2.isSnapshot());
@@ -399,6 +400,7 @@ public static void assertAreEqual(BlobProperties prop1, BlobProperties prop2) {
}
else {
assertNotNull(prop2);
+ assertEquals(prop1.getBlobType(), prop2.getBlobType());
assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition());
assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java
index da53166487e82..ecd82ba788f58 100644
--- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java
+++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java
@@ -19,6 +19,7 @@
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
+import java.security.InvalidKeyException;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumSet;
@@ -41,6 +42,7 @@
import com.microsoft.azure.storage.ResultContinuation;
import com.microsoft.azure.storage.ResultSegment;
import com.microsoft.azure.storage.SendingRequestEvent;
+import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature;
import com.microsoft.azure.storage.StorageErrorCodeStrings;
import com.microsoft.azure.storage.StorageEvent;
import com.microsoft.azure.storage.StorageException;
@@ -134,6 +136,86 @@ public void testCloudBlobContainerReference() throws StorageException, URISyntax
.toString());
}
+ @Test
+ @Category({ DevFabricTests.class, DevStoreTests.class })
+ public void testCloudBlobContainerReferenceFromServer() throws StorageException, URISyntaxException, IOException {
+ this.container.create();
+
+ CloudBlob blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, null, 1024, null);
+ blob.getProperties().setContentType("application/octet-stream");
+ blob.getProperties().setLength(1024);
+
+ CloudBlob blobRef = this.container.getBlobReferenceFromServer(blob.getName());
+ BlobTestHelper.assertAreEqual(blob, blobRef);
+
+ blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.PAGE_BLOB, null, 1024, null);
+ blob.getProperties().setContentType("application/octet-stream");
+ blob.getProperties().setLength(1024);
+
+ blobRef = this.container.getBlobReferenceFromServer(blob.getName());
+ BlobTestHelper.assertAreEqual(blob, blobRef);
+
+ blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.APPEND_BLOB, null, 1024, null);
+ blob.getProperties().setContentType("application/octet-stream");
+ blob.getProperties().setLength(1024);
+
+ blobRef = this.container.getBlobReferenceFromServer(blob.getName());
+ BlobTestHelper.assertAreEqual(blob, blobRef);
+ }
+
+ @Test
+ @Category({ DevFabricTests.class, DevStoreTests.class })
+ public void testCloudBlobContainerReferenceFromServerSnapshot() throws StorageException, URISyntaxException,
+ IOException {
+ this.container.create();
+
+ CloudBlob blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, null, 1024, null);
+ CloudBlob snapshot = blob.createSnapshot();
+ snapshot.getProperties().setContentType("application/octet-stream");
+ snapshot.getProperties().setLength(1024);
+
+ CloudBlob blobRef = this.container.getBlobReferenceFromServer(snapshot.getName(), snapshot.getSnapshotID(),
+ null, null, null);
+ BlobTestHelper.assertAreEqual(snapshot, blobRef);
+ }
+
+ @Test
+ @Category({ DevFabricTests.class, DevStoreTests.class })
+ public void testCloudBlobContainerReferenceFromServerSAS() throws StorageException, URISyntaxException,
+ IOException, InvalidKeyException {
+ this.container.create();
+ CloudBlob blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, null, 1024, null);
+
+ SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
+ Calendar now = Calendar.getInstance();
+ now.add(Calendar.MINUTE, 10);
+ policy.setSharedAccessExpiryTime(now.getTime());
+ policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ));
+ String token = this.container.generateSharedAccessSignature(policy, null);
+
+ CloudBlobContainer containerSAS = new CloudBlobContainer(this.container.getStorageUri(),
+ new StorageCredentialsSharedAccessSignature(token));
+ CloudBlob blobRef = containerSAS.getBlobReferenceFromServer(blob.getName());
+ assertEquals(blob.getClass(), blobRef.getClass());
+ assertEquals(blob.getUri(), blobRef.getUri());
+ }
+
+ @Test
+ @Category({ DevFabricTests.class, DevStoreTests.class })
+ public void testCloudBlobContainerReferenceFromServerMissingBlob() throws StorageException, URISyntaxException,
+ IOException {
+ this.container.create();
+
+ String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("missing");
+
+ try {
+ this.container.getBlobReferenceFromServer(blobName);
+ fail("Get reference from server should fail.");
+ } catch (StorageException ex) {
+ assertEquals(404, ex.getHttpStatusCode());
+ }
+ }
+
/**
* Create a container
*
diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobContainer.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobContainer.java
index 6ec136a0a2707..71b52668870b6 100644
--- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobContainer.java
+++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlobContainer.java
@@ -926,7 +926,139 @@ public CloudBlobDirectory getDirectoryReference(String directoryName) throws URI
return new CloudBlobDirectory(address, directoryName, this.blobServiceClient, this);
}
+
+
+ /**
+ * Gets a reference to a blob in this container. The blob must already exist on the service.
+ *
+ * Unlike the other get*Reference methods, this method does a service request to retrieve the blob's metadata and
+ * properties. The returned blob may be used directly as a CloudBlob or cast using either instanceof or
+ * getProperties().getBlobType() to determine its subtype.
+ *
+ * @param blobName
+ * A String
that represents the name of the blob.
+ *
+ * @return A {@link CloudBlob} object that represents a reference to the specified blob.
+ *
+ * @throws StorageException
+ * If a storage service error occurred.
+ * @throws URISyntaxException
+ * If the resource URI is invalid.
+ */
+ @DoesServiceRequest
+ public final CloudBlob getBlobReferenceFromServer(final String blobName) throws URISyntaxException, StorageException {
+ return this.getBlobReferenceFromServer(blobName, null, null, null, null);
+ }
+ /**
+ * Gets a reference to a blob in this container. The blob must already exist on the service.
+ *
+ * Unlike the other get*Reference methods, this method does a service request to retrieve the blob's metadata and
+ * properties. The returned blob may be used directly as a CloudBlob or cast using either instanceof or
+ * getProperties().getBlobType() to determine its subtype.
+ *
+ * @param blobName
+ * A String
that represents the name of the blob.
+ * @param snapshotID
+ * A String
that represents the snapshot ID of the blob.
+ * @param accessCondition
+ * An {@link AccessCondition} object that represents the access conditions for the blob.
+ * @param options
+ * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying
+ * 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 {@link CloudBlob} object that represents a reference to the specified blob.
+ *
+ * @throws StorageException
+ * If a storage service error occurred.
+ * @throws URISyntaxException
+ * If the resource URI is invalid.
+ */
+ @DoesServiceRequest
+ public final CloudBlob getBlobReferenceFromServer(final String blobName, final String snapshotID,
+ AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext)
+ throws URISyntaxException, StorageException {
+ if (opContext == null) {
+ opContext = new OperationContext();
+ }
+ options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.UNSPECIFIED, this.blobServiceClient);
+
+ StorageUri blobUri = PathUtility.appendPathToUri(this.getStorageUri(), blobName);
+ return ExecutionEngine.executeWithRetry(this.blobServiceClient, this,
+ this.getBlobReferenceFromServerImpl(blobName, blobUri, snapshotID, accessCondition, options), options.getRetryPolicyFactory(), opContext);
+ }
+
+ private StorageRequest getBlobReferenceFromServerImpl(
+ final String blobName, final StorageUri blobUri, final String snapshotID, 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, CloudBlobContainer container,
+ OperationContext context) throws Exception {
+ StorageUri transformedblobUri = CloudBlobContainer.this.getServiceClient().getCredentials().transformUri(blobUri, context);
+ return BlobRequest.getBlobProperties(transformedblobUri.getUri(this.getCurrentLocation()), options, context, accessCondition, snapshotID);
+ }
+
+ @Override
+ public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context)
+ throws Exception {
+ StorageRequest.signBlobQueueAndFileRequest(connection, client, -1L, context);
+ }
+
+ @Override
+ public CloudBlob preProcessResponse(CloudBlobContainer container, CloudBlobClient client, OperationContext context)
+ throws Exception {
+ if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) {
+ this.setNonExceptionedRetryableFailure(true);
+ return null;
+ }
+
+ // Set attributes
+ final BlobAttributes retrievedAttributes = BlobResponse.getBlobAttributes(this.getConnection(),
+ blobUri, snapshotID);
+
+ CloudBlob blob;
+ switch (retrievedAttributes.getProperties().getBlobType()) {
+ case APPEND_BLOB:
+ blob = container.getAppendBlobReference(blobName, snapshotID);
+ break;
+
+ case BLOCK_BLOB:
+ blob = container.getBlockBlobReference(blobName, snapshotID);
+ break;
+
+ case PAGE_BLOB:
+ blob = container.getPageBlobReference(blobName, snapshotID);
+ break;
+
+ default:
+ throw new StorageException(StorageErrorCodeStrings.INCORRECT_BLOB_TYPE,
+ SR.INVALID_RESPONSE_RECEIVED, Constants.HeaderConstants.HTTP_UNUSED_306, null, null);
+ }
+
+ blob.properties = retrievedAttributes.getProperties();
+ blob.metadata = retrievedAttributes.getMetadata();
+
+ return blob;
+ }
+ };
+
+ return getRequest;
+ }
+
+
/**
* Returns the metadata for the container. This value is initialized with the metadata from the queue by a call to
* {@link #downloadAttributes}, and is set on the queue with a call to {@link #uploadMetadata}.