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