From 66be278907e3f2e4b3b53feca43d7c7193d60ce6 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Wed, 10 Jul 2019 13:49:02 -0700 Subject: [PATCH 01/19] Removed raw clients --- .../storage/blob/AppendBlobAsyncClient.java | 47 +- .../blob/AppendBlobAsyncRawClient.java | 192 ----- .../azure/storage/blob/AppendBlobClient.java | 17 +- .../azure/storage/blob/BlobAsyncClient.java | 208 +++-- .../storage/blob/BlobAsyncRawClient.java | 766 ------------------ .../com/azure/storage/blob/BlobClient.java | 3 +- .../azure/storage/blob/BlobInputStream.java | 2 +- .../azure/storage/blob/BlobOutputStream.java | 4 +- .../com/azure/storage/blob/BlobURLParts.java | 8 +- .../storage/blob/BlockBlobAsyncClient.java | 50 +- .../storage/blob/BlockBlobAsyncRawClient.java | 364 --------- .../azure/storage/blob/BlockBlobClient.java | 14 +- .../storage/blob/ContainerAsyncClient.java | 290 +++++-- .../storage/blob/ContainerAsyncRawClient.java | 708 ---------------- .../azure/storage/blob/ContainerClient.java | 7 +- .../storage/blob/ContainerRawClient.java | 652 --------------- .../storage/blob/PageBlobAsyncClient.java | 210 +++-- .../storage/blob/PageBlobAsyncRawClient.java | 620 -------------- .../azure/storage/blob/PageBlobClient.java | 12 +- .../storage/blob/StorageAsyncClient.java | 84 +- .../storage/blob/StorageAsyncRawClient.java | 172 ---- .../com/azure/storage/blob/StorageClient.java | 3 +- .../azure/storage/blob/StorageRawClient.java | 259 ------ .../java/com/azure/storage/blob/Sample.java | 6 +- 24 files changed, 717 insertions(+), 3981 deletions(-) delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/ContainerRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/StorageRawClient.java diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncClient.java index 7b4585580e16f..60376a3d990ab 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncClient.java @@ -5,6 +5,7 @@ import com.azure.core.http.rest.Response; import com.azure.core.http.rest.SimpleResponse; +import com.azure.core.util.Context; import com.azure.storage.blob.implementation.AzureBlobStorageBuilder; import com.azure.storage.blob.models.AppendBlobAccessConditions; import com.azure.storage.blob.models.AppendBlobItem; @@ -13,12 +14,13 @@ import com.azure.storage.blob.models.BlobRange; import com.azure.storage.blob.models.Metadata; import com.azure.storage.blob.models.SourceModifiedAccessConditions; -import io.netty.buffer.Unpooled; +import io.netty.buffer.ByteBuf; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.net.URL; -import java.nio.ByteBuffer; + +import static com.azure.storage.blob.Utility.postProcessResponse; /** @@ -45,8 +47,6 @@ * object through {@link Mono#toFuture()}. */ public final class AppendBlobAsyncClient extends BlobAsyncClient { - final AppendBlobAsyncRawClient appendBlobAsyncRawClient; - /** * Indicates the maximum number of bytes that can be sent in a call to appendBlock. */ @@ -63,7 +63,6 @@ public final class AppendBlobAsyncClient extends BlobAsyncClient { */ AppendBlobAsyncClient(AzureBlobStorageBuilder azureBlobStorageBuilder, String snapshot) { super(azureBlobStorageBuilder, snapshot); - appendBlobAsyncRawClient = new AppendBlobAsyncRawClient(azureBlobStorageBuilder.build()); } /** @@ -90,8 +89,13 @@ public Mono> create() { * A reactive response containing the information of the created appended blob. */ public Mono> create(BlobHTTPHeaders headers, Metadata metadata, BlobAccessConditions accessConditions) { - return appendBlobAsyncRawClient - .create(headers, metadata, accessConditions) + metadata = (metadata == null) ? new Metadata() : metadata; + accessConditions = (accessConditions == null) ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.appendBlobs().createWithRestResponseAsync(null, + null, 0, null, metadata, null, null, + null, null, headers, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new AppendBlobItem(rb.deserializedHeaders()))); } @@ -111,7 +115,7 @@ public Mono> create(BlobHTTPHeaders headers, Metadata m * @return * A reactive response containing the information of the append blob operation. */ - public Mono> appendBlock(Flux data, long length) { + public Mono> appendBlock(Flux data, long length) { return this.appendBlock(data, length, null); } @@ -133,10 +137,17 @@ public Mono> appendBlock(Flux data, long le * @return * A reactive response containing the information of the append blob operation. */ - public Mono> appendBlock(Flux data, long length, - AppendBlobAccessConditions appendBlobAccessConditions) { - return appendBlobAsyncRawClient - .appendBlock(data.map(Unpooled::wrappedBuffer), length, appendBlobAccessConditions) + public Mono> appendBlock(Flux data, long length, + AppendBlobAccessConditions appendBlobAccessConditions) { + appendBlobAccessConditions = appendBlobAccessConditions == null ? new AppendBlobAccessConditions() + : appendBlobAccessConditions; + + return postProcessResponse(this.azureBlobStorage.appendBlobs().appendBlockWithRestResponseAsync( + null, null, data, length, null, null, + null, null, null, null, + appendBlobAccessConditions.leaseAccessConditions(), + appendBlobAccessConditions.appendPositionAccessConditions(), + appendBlobAccessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new AppendBlobItem(rb.deserializedHeaders()))); } @@ -183,8 +194,16 @@ public Mono> appendBlockFromUrl(URL sourceURL, BlobRang public Mono> appendBlockFromUrl(URL sourceURL, BlobRange sourceRange, byte[] sourceContentMD5, AppendBlobAccessConditions destAccessConditions, SourceModifiedAccessConditions sourceAccessConditions) { - return appendBlobAsyncRawClient - .appendBlockFromUrl(sourceURL, sourceRange, sourceContentMD5, destAccessConditions, sourceAccessConditions) + sourceRange = sourceRange == null ? new BlobRange(0) : sourceRange; + destAccessConditions = destAccessConditions == null + ? new AppendBlobAccessConditions() : destAccessConditions; + + return postProcessResponse( + this.azureBlobStorage.appendBlobs().appendBlockFromUrlWithRestResponseAsync(null, null, + sourceURL, 0, sourceRange.toString(), sourceContentMD5, null, null, + destAccessConditions.leaseAccessConditions(), + destAccessConditions.appendPositionAccessConditions(), + destAccessConditions.modifiedAccessConditions(), sourceAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new AppendBlobItem(rb.deserializedHeaders()))); } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncRawClient.java deleted file mode 100644 index 15bf8e09e4d1f..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncRawClient.java +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.util.Context; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.AppendBlobAccessConditions; -import com.azure.storage.blob.models.AppendBlobsAppendBlockFromUrlResponse; -import com.azure.storage.blob.models.AppendBlobsAppendBlockResponse; -import com.azure.storage.blob.models.AppendBlobsCreateResponse; -import com.azure.storage.blob.models.BlobAccessConditions; -import com.azure.storage.blob.models.BlobHTTPHeaders; -import com.azure.storage.blob.models.BlobRange; -import com.azure.storage.blob.models.Metadata; -import com.azure.storage.blob.models.SourceModifiedAccessConditions; -import io.netty.buffer.ByteBuf; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.net.URL; - -import static com.azure.storage.blob.Utility.postProcessResponse; - - -/** - * Represents a URL to an append blob. It may be obtained by direct construction or via the create method on a - * {@link ContainerAsyncClient} object. This class does not hold any state about a particular append blob but is instead a - * convenient way of sending off appropriate requests to the resource on the service. Please refer to the - * Azure Docs - */ -final class AppendBlobAsyncRawClient extends BlobAsyncRawClient { - - /** - * Indicates the maximum number of bytes that can be sent in a call to appendBlock. - */ - public static final int MAX_APPEND_BLOCK_BYTES = 4 * Constants.MB; - - /** - * Indicates the maximum number of blocks allowed in an append blob. - */ - public static final int MAX_BLOCKS = 50000; - - /** - * Creates a {@code AppendBlobAsyncRawClient} object pointing to the account specified by the URL and using the provided - * pipeline to make HTTP requests. - */ - AppendBlobAsyncRawClient(AzureBlobStorageImpl azureBlobStorage) { - super(azureBlobStorage, null); - } - - /** - * Creates a 0-length append blob. Call AppendBlock to append data to an append blob. For more information, see - * the Azure Docs. - * - * @return Emits the successful response. - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=append_blob "Sample code for AppendBlobAsyncRawClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono create() { - return this.create(null, null, null); - } - - /** - * Creates a 0-length append blob. Call AppendBlock to append data to an append blob. For more information, see - * the Azure Docs. - * - * @param headers {@link BlobHTTPHeaders} - * @param metadata {@link Metadata} - * @param accessConditions {@link BlobAccessConditions} - * @return Emits the successful response. - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=append_blob "Sample code for AppendBlobAsyncRawClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono create(BlobHTTPHeaders headers, Metadata metadata, - BlobAccessConditions accessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.appendBlobs().createWithRestResponseAsync(null, - null, 0, null, metadata, null, null, - null, null, headers, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Commits a new block of data to the end of the existing append blob. For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - * - * @param data The data to write to the blob. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param length The exact length of the data. It is important that this value match precisely the length of the data - * emitted by the {@code Flux}. - * @return Emits the successful response. - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=append_blob "Sample code for AppendBlobAsyncRawClient.appendBlock")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono appendBlock(Flux data, long length) { - return this.appendBlock(data, length, null); - } - - /** - * Commits a new block of data to the end of the existing append blob. For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - * - * @param data The data to write to the blob. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param length The exact length of the data. It is important that this value match precisely the length of the data - * emitted by the {@code Flux}. - * @param appendBlobAccessConditions {@link AppendBlobAccessConditions} - * @return Emits the successful response. - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=append_blob "Sample code for AppendBlobAsyncRawClient.appendBlock")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono appendBlock(Flux data, long length, - AppendBlobAccessConditions appendBlobAccessConditions) { - appendBlobAccessConditions = appendBlobAccessConditions == null ? new AppendBlobAccessConditions() - : appendBlobAccessConditions; - - return postProcessResponse(this.azureBlobStorage.appendBlobs().appendBlockWithRestResponseAsync( - null, null, data, length, null, null, - null, null, null, null, - appendBlobAccessConditions.leaseAccessConditions(), - appendBlobAccessConditions.appendPositionAccessConditions(), - appendBlobAccessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Commits a new block of data from another blob to the end of this append blob. For more information, see the - * Azure Docs. - *

- * - * @param sourceURL The url to the blob that will be the source of the copy. A source blob in the same storage account can - * be authenticated via Shared Key. However, if the source is a blob in another account, the source blob - * must either be public or must be authenticated via a shared access signature. If the source blob is - * public, no authentication is required to perform the operation. - * @param sourceRange The source {@link BlobRange} to copy. - * @return Emits the successful response. - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=append_from_url "Sample code for AppendBlobAsyncRawClient.appendBlockFromUrl")] - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono appendBlockFromUrl(URL sourceURL, BlobRange sourceRange) { - return this.appendBlockFromUrl(sourceURL, sourceRange, null, null, - null); - } - - /** - * Commits a new block of data from another blob to the end of this append blob. For more information, see the - * Azure Docs. - *

- * - * @param sourceURL The url to the blob that will be the source of the copy. A source blob in the same storage account can - * be authenticated via Shared Key. However, if the source is a blob in another account, the source blob - * must either be public or must be authenticated via a shared access signature. If the source blob is - * public, no authentication is required to perform the operation. - * @param sourceRange {@link BlobRange} - * @param sourceContentMD5 An MD5 hash of the block content from the source blob. If specified, the service will calculate the MD5 - * of the received data and fail the request if it does not match the provided MD5. - * @param destAccessConditions {@link AppendBlobAccessConditions} - * @param sourceAccessConditions {@link SourceModifiedAccessConditions} - * @return Emits the successful response. - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=append_from_url "Sample code for AppendBlobAsyncRawClient.appendBlockFromUrl")] - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono appendBlockFromUrl(URL sourceURL, BlobRange sourceRange, - byte[] sourceContentMD5, AppendBlobAccessConditions destAccessConditions, - SourceModifiedAccessConditions sourceAccessConditions) { - - sourceRange = sourceRange == null ? new BlobRange(0) : sourceRange; - destAccessConditions = destAccessConditions == null - ? new AppendBlobAccessConditions() : destAccessConditions; - - return postProcessResponse( - this.azureBlobStorage.appendBlobs().appendBlockFromUrlWithRestResponseAsync(null, null, - sourceURL, 0, sourceRange.toString(), sourceContentMD5, null, null, - destAccessConditions.leaseAccessConditions(), - destAccessConditions.appendPositionAccessConditions(), - destAccessConditions.modifiedAccessConditions(), sourceAccessConditions, Context.NONE)); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobClient.java index 47a379649659f..6be2bf7f4e5aa 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobClient.java @@ -11,13 +11,14 @@ import com.azure.storage.blob.models.BlobRange; import com.azure.storage.blob.models.Metadata; import com.azure.storage.blob.models.SourceModifiedAccessConditions; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import java.io.InputStream; import java.net.URL; -import java.nio.ByteBuffer; import java.time.Duration; @@ -37,18 +38,17 @@ * for more information. */ public final class AppendBlobClient extends BlobClient { - - AppendBlobAsyncClient appendBlobAsyncClient; + private AppendBlobAsyncClient appendBlobAsyncClient; /** * Indicates the maximum number of bytes that can be sent in a call to appendBlock. */ - public static final int MAX_APPEND_BLOCK_BYTES = 4 * Constants.MB; + public static final int MAX_APPEND_BLOCK_BYTES = AppendBlobAsyncClient.MAX_APPEND_BLOCK_BYTES; /** * Indicates the maximum number of blocks allowed in an append blob. */ - public static final int MAX_BLOCKS = 50000; + public static final int MAX_BLOCKS = AppendBlobAsyncClient.MAX_BLOCKS; /** * Package-private constructor for use by {@link AppendBlobClientBuilder}. @@ -58,7 +58,7 @@ public final class AppendBlobClient extends BlobClient { super(appendBlobAsyncClient); this.appendBlobAsyncClient = appendBlobAsyncClient; } - + /** * Creates and opens an output stream to write data to the append blob. If the blob already exists on the service, * it will be overwritten. @@ -160,7 +160,7 @@ public Response appendBlock(InputStream data, long length) { */ public Response appendBlock(InputStream data, long length, AppendBlobAccessConditions appendBlobAccessConditions, Duration timeout) { - Flux fbb = Flux.range(0, (int) Math.ceil((double) length / (double) MAX_APPEND_BLOCK_BYTES)) + Flux fbb = Flux.range(0, (int) Math.ceil((double) length / (double) MAX_APPEND_BLOCK_BYTES)) .map(i -> i * MAX_APPEND_BLOCK_BYTES) .concatMap(pos -> Mono.fromCallable(() -> { long count = pos + MAX_APPEND_BLOCK_BYTES > length ? length - pos : MAX_APPEND_BLOCK_BYTES; @@ -169,7 +169,8 @@ public Response appendBlock(InputStream data, long length, while (read < count) { read += data.read(cache, read, (int) count - read); } - return ByteBuffer.wrap(cache); + + return ByteBufAllocator.DEFAULT.buffer((int) count).writeBytes(cache); })); Mono> response = appendBlobAsyncClient.appendBlock(fbb.subscribeOn(Schedulers.elastic()), length, appendBlobAccessConditions); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java index 7ec72ecf30b53..fa4a0e77edac5 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java @@ -9,7 +9,9 @@ import com.azure.core.http.rest.VoidResponse; import com.azure.core.implementation.http.UrlBuilder; import com.azure.core.implementation.util.FluxUtil; +import com.azure.core.util.Context; import com.azure.storage.blob.implementation.AzureBlobStorageBuilder; +import com.azure.storage.blob.implementation.AzureBlobStorageImpl; import com.azure.storage.blob.models.AccessTier; import com.azure.storage.blob.models.BlobAccessConditions; import com.azure.storage.blob.models.BlobHTTPHeaders; @@ -20,6 +22,7 @@ import com.azure.storage.blob.models.Metadata; import com.azure.storage.blob.models.ModifiedAccessConditions; import com.azure.storage.blob.models.ReliableDownloadOptions; +import com.azure.storage.blob.models.SourceModifiedAccessConditions; import com.azure.storage.blob.models.StorageAccountInfo; import io.netty.buffer.ByteBuf; import reactor.core.publisher.Flux; @@ -38,6 +41,8 @@ import java.util.ArrayList; import java.util.List; +import static com.azure.storage.blob.Utility.postProcessResponse; + /** * Client to a blob of any type: block, append, or page. It may only be instantiated through a {@link BlobClientBuilder} or via * the method {@link ContainerAsyncClient#getBlobAsyncClient(String)}. This class does not hold any state about a particular @@ -68,14 +73,16 @@ public class BlobAsyncClient { private static final int BLOB_DEFAULT_DOWNLOAD_BLOCK_SIZE = 4 * Constants.MB; private static final int BLOB_MAX_DOWNLOAD_BLOCK_SIZE = 100 * Constants.MB; - final BlobAsyncRawClient blobAsyncRawClient; + protected final AzureBlobStorageImpl azureBlobStorage; + protected final String snapshot; /** * Package-private constructor for use by {@link BlobClientBuilder}. * @param azureBlobStorageBuilder the API client builder for blob storage API */ BlobAsyncClient(AzureBlobStorageBuilder azureBlobStorageBuilder, String snapshot) { - this.blobAsyncRawClient = new BlobAsyncRawClient(azureBlobStorageBuilder.build(), snapshot); + this.azureBlobStorage = azureBlobStorageBuilder.build(); + this.snapshot = snapshot; } /** @@ -86,7 +93,9 @@ public class BlobAsyncClient { * A {@link BlockBlobAsyncClient} to this resource. */ public BlockBlobAsyncClient asBlockBlobAsyncClient() { - return new BlockBlobAsyncClient(new AzureBlobStorageBuilder().url(getBlobUrl().toString()).pipeline(blobAsyncRawClient.azureBlobStorage.httpPipeline()), blobAsyncRawClient.snapshot); + return new BlockBlobAsyncClient(new AzureBlobStorageBuilder() + .url(getBlobUrl().toString()) + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** @@ -97,7 +106,9 @@ public BlockBlobAsyncClient asBlockBlobAsyncClient() { * A {@link AppendBlobAsyncClient} to this resource. */ public AppendBlobAsyncClient asAppendBlobAsyncClient() { - return new AppendBlobAsyncClient(new AzureBlobStorageBuilder().url(getBlobUrl().toString()).pipeline(blobAsyncRawClient.azureBlobStorage.httpPipeline()), blobAsyncRawClient.snapshot); + return new AppendBlobAsyncClient(new AzureBlobStorageBuilder() + .url(getBlobUrl().toString()) + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** @@ -108,7 +119,9 @@ public AppendBlobAsyncClient asAppendBlobAsyncClient() { * A {@link PageBlobAsyncClient} to this resource. */ public PageBlobAsyncClient asPageBlobAsyncClient() { - return new PageBlobAsyncClient(new AzureBlobStorageBuilder().url(getBlobUrl().toString()).pipeline(blobAsyncRawClient.azureBlobStorage.httpPipeline()), blobAsyncRawClient.snapshot); + return new PageBlobAsyncClient(new AzureBlobStorageBuilder() + .url(getBlobUrl().toString()) + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** @@ -122,7 +135,7 @@ public ContainerAsyncClient getContainerAsyncClient() { BlobURLParts parts = URLParser.parse(getBlobUrl()); return new ContainerAsyncClient(new AzureBlobStorageBuilder() .url(String.format("%s://%s/%s", parts.scheme(), parts.host(), parts.containerName())) - .pipeline(blobAsyncRawClient.azureBlobStorage.httpPipeline())); + .pipeline(azureBlobStorage.httpPipeline())); } /** @@ -132,13 +145,13 @@ public ContainerAsyncClient getContainerAsyncClient() { */ public URL getBlobUrl() { try { - UrlBuilder urlBuilder = UrlBuilder.parse(blobAsyncRawClient.azureBlobStorage.url()); - if (blobAsyncRawClient.snapshot != null) { - urlBuilder.query("snapshot=" + blobAsyncRawClient.snapshot); + UrlBuilder urlBuilder = UrlBuilder.parse(azureBlobStorage.url()); + if (snapshot != null) { + urlBuilder.query("snapshot=" + snapshot); } return urlBuilder.toURL(); } catch (MalformedURLException e) { - throw new RuntimeException(String.format("Invalid URL on %s: %s" + getClass().getSimpleName(), blobAsyncRawClient.azureBlobStorage.url()), e); + throw new RuntimeException(String.format("Invalid URL on %s: %s" + getClass().getSimpleName(), azureBlobStorage.url()), e); } } @@ -192,8 +205,21 @@ public Mono> startCopyFromURL(URL sourceURL) { */ public Mono> startCopyFromURL(URL sourceURL, Metadata metadata, ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions) { - return blobAsyncRawClient - .startCopyFromURL(sourceURL, metadata, sourceModifiedAccessConditions, destAccessConditions) + metadata = metadata == null ? new Metadata() : metadata; + sourceModifiedAccessConditions = sourceModifiedAccessConditions == null + ? new ModifiedAccessConditions() : sourceModifiedAccessConditions; + destAccessConditions = destAccessConditions == null ? new BlobAccessConditions() : destAccessConditions; + + // We want to hide the SourceAccessConditions type from the user for consistency's sake, so we convert here. + SourceModifiedAccessConditions sourceConditions = new SourceModifiedAccessConditions() + .sourceIfModifiedSince(sourceModifiedAccessConditions.ifModifiedSince()) + .sourceIfUnmodifiedSince(sourceModifiedAccessConditions.ifUnmodifiedSince()) + .sourceIfMatch(sourceModifiedAccessConditions.ifMatch()) + .sourceIfNoneMatch(sourceModifiedAccessConditions.ifNoneMatch()); + + return postProcessResponse(this.azureBlobStorage.blobs().startCopyFromURLWithRestResponseAsync( + null, null, sourceURL, null, metadata, null, sourceConditions, + destAccessConditions.modifiedAccessConditions(), destAccessConditions.leaseAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().copyId())); } @@ -225,8 +251,8 @@ public Mono abortCopyFromURL(String copyId) { * A reactive response signalling completion. */ public Mono abortCopyFromURL(String copyId, LeaseAccessConditions leaseAccessConditions) { - return blobAsyncRawClient - .abortCopyFromURL(copyId, leaseAccessConditions) + return postProcessResponse(this.azureBlobStorage.blobs().abortCopyFromURLWithRestResponseAsync( + null, null, copyId, null, null, leaseAccessConditions, Context.NONE)) .map(VoidResponse::new); } @@ -263,8 +289,21 @@ public Mono> copyFromURL(URL copySource) { */ public Mono> copyFromURL(URL copySource, Metadata metadata, ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions) { - return blobAsyncRawClient - .syncCopyFromURL(copySource, metadata, sourceModifiedAccessConditions, destAccessConditions) + metadata = metadata == null ? new Metadata() : metadata; + sourceModifiedAccessConditions = sourceModifiedAccessConditions == null + ? new ModifiedAccessConditions() : sourceModifiedAccessConditions; + destAccessConditions = destAccessConditions == null ? new BlobAccessConditions() : destAccessConditions; + + // We want to hide the SourceAccessConditions type from the user for consistency's sake, so we convert here. + SourceModifiedAccessConditions sourceConditions = new SourceModifiedAccessConditions() + .sourceIfModifiedSince(sourceModifiedAccessConditions.ifModifiedSince()) + .sourceIfUnmodifiedSince(sourceModifiedAccessConditions.ifUnmodifiedSince()) + .sourceIfMatch(sourceModifiedAccessConditions.ifMatch()) + .sourceIfNoneMatch(sourceModifiedAccessConditions.ifNoneMatch()); + + return postProcessResponse(this.azureBlobStorage.blobs().copyFromURLWithRestResponseAsync( + null, null, copySource, null, metadata, null, sourceConditions, + destAccessConditions.modifiedAccessConditions(), destAccessConditions.leaseAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().copyId())); } @@ -293,13 +332,60 @@ public Mono>> download() { */ public Mono>> download(BlobRange range, BlobAccessConditions accessConditions, boolean rangeGetContentMD5, ReliableDownloadOptions options) { - return blobAsyncRawClient - .download(range, accessConditions, rangeGetContentMD5) + return this.download(range, accessConditions, rangeGetContentMD5) .map(response -> new SimpleResponse<>( response.rawResponse(), response.body(options).map(ByteBuf::nioBuffer).switchIfEmpty(Flux.just(ByteBuffer.allocate(0))))); } + /** + * Reads a range of bytes from a blob. The response also includes the blob's properties and metadata. For more + * information, see the Azure Docs. + *

+ * Note that the response body has reliable download functionality built in, meaning that a failed download stream + * will be automatically retried. This behavior may be configured with {@link ReliableDownloadOptions}. + * + * @param range + * {@link BlobRange} + * @param accessConditions + * {@link BlobAccessConditions} + * @param rangeGetContentMD5 + * Whether the contentMD5 for the specified blob range should be returned. + * + * @return Emits the successful response. + * + * @apiNote ## Sample Code \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=upload_download "Sample code for BlobAsyncClient.download")] \n + * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) + */ + Mono download(BlobRange range, BlobAccessConditions accessConditions, boolean rangeGetContentMD5) { + range = range == null ? new BlobRange(0) : range; + Boolean getMD5 = rangeGetContentMD5 ? rangeGetContentMD5 : null; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + HTTPGetterInfo info = new HTTPGetterInfo() + .offset(range.offset()) + .count(range.count()) + .eTag(accessConditions.modifiedAccessConditions().ifMatch()); + + // TODO: range is BlobRange but expected as String + // TODO: figure out correct response + return postProcessResponse(this.azureBlobStorage.blobs().downloadWithRestResponseAsync( + null, null, snapshot, null, null, range.toHeaderValue(), getMD5, + null, null, null, null, + accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) + // Convert the autorest response to a DownloadAsyncResponse, which enable reliable download. + .map(response -> { + // If there wasn't an etag originally specified, lock on the one returned. + info.eTag(response.deserializedHeaders().eTag()); + return new DownloadAsyncResponse(response, info, + // In the event of a stream failure, make a new request to pick up where we left off. + newInfo -> + this.download(new BlobRange(newInfo.offset(), newInfo.count()), + new BlobAccessConditions().modifiedAccessConditions( + new ModifiedAccessConditions().ifMatch(info.eTag())), false)); + }); + } + /** * Downloads the entire blob into a file specified by the path. The file will be created if it doesn't exist. * Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link AppendBlobClient}. @@ -346,8 +432,7 @@ public Mono downloadToFile(String filePath, BlobRange range, Integer block channel -> Mono.justOrEmpty(range) .switchIfEmpty(getFullBlobRange(accessConditions)) .flatMapMany(rg -> Flux.fromIterable(sliceBlobRange(rg, blockSize))) - .flatMap(chunk -> blobAsyncRawClient - .download(chunk, accessConditions, rangeGetContentMD5) + .flatMap(chunk -> this.download(chunk, accessConditions, rangeGetContentMD5) .subscribeOn(Schedulers.elastic()) .flatMap(dar -> FluxUtil.bytebufStreamToFile(dar.body(options), channel, chunk.offset() - (range == null ? 0 : range.offset())))) .then(), this::downloadToFileCleanup); @@ -415,8 +500,12 @@ public Mono delete() { */ public Mono delete(DeleteSnapshotsOptionType deleteBlobSnapshotOptions, BlobAccessConditions accessConditions) { - return blobAsyncRawClient - .delete(deleteBlobSnapshotOptions, accessConditions) + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blobs().deleteWithRestResponseAsync( + null, null, snapshot, null, null, deleteBlobSnapshotOptions, + null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), + Context.NONE)) .map(VoidResponse::new); } @@ -440,8 +529,12 @@ public Mono> getProperties() { * A reactive response containing the blob properties and metadata. */ public Mono> getProperties(BlobAccessConditions accessConditions) { - return blobAsyncRawClient - .getProperties(accessConditions) + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blobs().getPropertiesWithRestResponseAsync( + null, null, snapshot, null, null, null, + null, null, null, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new BlobProperties(rb.deserializedHeaders()))); } @@ -476,8 +569,11 @@ public Mono setHTTPHeaders(BlobHTTPHeaders headers) { * A reactive response signalling completion. */ public Mono setHTTPHeaders(BlobHTTPHeaders headers, BlobAccessConditions accessConditions) { - return blobAsyncRawClient - .setHTTPHeaders(headers, accessConditions) + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blobs().setHTTPHeadersWithRestResponseAsync( + null, null, null, null, headers, + accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) .map(VoidResponse::new); } @@ -510,8 +606,13 @@ public Mono setMetadata(Metadata metadata) { * A reactive response signalling completion. */ public Mono setMetadata(Metadata metadata, BlobAccessConditions accessConditions) { - return blobAsyncRawClient - .setMetadata(metadata, accessConditions) + metadata = metadata == null ? new Metadata() : metadata; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blobs().setMetadataWithRestResponseAsync( + null, null, null, metadata, null, null, + null, null, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .map(VoidResponse::new); } @@ -537,8 +638,13 @@ public Mono> createSnapshot() { * A reactive response containing the ID of the new snapshot. */ public Mono> createSnapshot(Metadata metadata, BlobAccessConditions accessConditions) { - return blobAsyncRawClient - .createSnapshot(metadata, accessConditions) + metadata = metadata == null ? new Metadata() : metadata; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blobs().createSnapshotWithRestResponseAsync( + null, null, null, metadata, null, null, + null, null, accessConditions.modifiedAccessConditions(), + accessConditions.leaseAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().snapshot())); } @@ -572,8 +678,10 @@ public Mono setTier(AccessTier tier) { * A reactive response signalling completion. */ public Mono setTier(AccessTier tier, LeaseAccessConditions leaseAccessConditions) { - return blobAsyncRawClient - .setTier(tier, leaseAccessConditions) + Utility.assertNotNull("tier", tier); + + return postProcessResponse(this.azureBlobStorage.blobs().setTierWithRestResponseAsync( + null, null, tier, null, null, leaseAccessConditions, Context.NONE)) .map(VoidResponse::new); } @@ -584,8 +692,8 @@ public Mono setTier(AccessTier tier, LeaseAccessConditions leaseAc * A reactive response signalling completion. */ public Mono undelete() { - return blobAsyncRawClient - .undelete() + return postProcessResponse(this.azureBlobStorage.blobs().undeleteWithRestResponseAsync(null, + null, Context.NONE)) .map(VoidResponse::new); } @@ -622,10 +730,18 @@ public Mono> acquireLease(String proposedId, int duration) { * * @return * A reactive response containing the lease ID. + * @throws IllegalArgumentException If {@code duration} is outside the bounds of 15 to 60 or isn't -1. */ public Mono> acquireLease(String proposedID, int duration, ModifiedAccessConditions modifiedAccessConditions) { - return blobAsyncRawClient - .acquireLease(proposedID, duration, modifiedAccessConditions) + if (!(duration == -1 || (duration >= 15 && duration <= 60))) { + // Throwing is preferred to Mono.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("Duration must be -1 or between 15 and 60."); + } + + return postProcessResponse(this.azureBlobStorage.blobs().acquireLeaseWithRestResponseAsync( + null, null, null, duration, proposedID, null, + modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseId())); } @@ -656,8 +772,8 @@ public Mono> renewLease(String leaseID) { * A reactive response containing the renewed lease ID. */ public Mono> renewLease(String leaseID, ModifiedAccessConditions modifiedAccessConditions) { - return blobAsyncRawClient - .renewLease(leaseID, modifiedAccessConditions) + return postProcessResponse(this.azureBlobStorage.blobs().renewLeaseWithRestResponseAsync(null, + null, leaseID, null, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseId())); } @@ -688,8 +804,8 @@ public Mono releaseLease(String leaseID) { * A reactive response signalling completion. */ public Mono releaseLease(String leaseID, ModifiedAccessConditions modifiedAccessConditions) { - return blobAsyncRawClient - .releaseLease(leaseID, modifiedAccessConditions) + return postProcessResponse(this.azureBlobStorage.blobs().releaseLeaseWithRestResponseAsync(null, + null, leaseID, null, null, modifiedAccessConditions, Context.NONE)) .map(VoidResponse::new); } @@ -723,8 +839,8 @@ public Mono> breakLease() { * A reactive response containing the remaining time in the broken lease in seconds. */ public Mono> breakLease(Integer breakPeriodInSeconds, ModifiedAccessConditions modifiedAccessConditions) { - return blobAsyncRawClient - .breakLease(breakPeriodInSeconds, modifiedAccessConditions) + return postProcessResponse(this.azureBlobStorage.blobs().breakLeaseWithRestResponseAsync(null, + null, null, breakPeriodInSeconds, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseTime())); } @@ -758,8 +874,8 @@ public Mono> changeLease(String leaseId, String proposedID) { * @return A reactive response containing the new lease ID. */ public Mono> changeLease(String leaseId, String proposedID, ModifiedAccessConditions modifiedAccessConditions) { - return blobAsyncRawClient - .changeLease(leaseId, proposedID, modifiedAccessConditions) + return postProcessResponse(this.azureBlobStorage.blobs().changeLeaseWithRestResponseAsync(null, + null, leaseId, proposedID, null, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseId())); } @@ -770,8 +886,8 @@ public Mono> changeLease(String leaseId, String proposedID, Mod */ // TODO (unknown): determine this return type public Mono> getAccountInfo() { - return blobAsyncRawClient - .getAccountInfo() + return postProcessResponse( + this.azureBlobStorage.blobs().getAccountInfoWithRestResponseAsync(null, null, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new StorageAccountInfo(rb.deserializedHeaders()))); } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncRawClient.java deleted file mode 100644 index 002ba761a3bf8..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncRawClient.java +++ /dev/null @@ -1,766 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.util.Context; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.AccessTier; -import com.azure.storage.blob.models.BlobAccessConditions; -import com.azure.storage.blob.models.BlobHTTPHeaders; -import com.azure.storage.blob.models.BlobRange; -import com.azure.storage.blob.models.BlobStartCopyFromURLHeaders; -import com.azure.storage.blob.models.BlobsAbortCopyFromURLResponse; -import com.azure.storage.blob.models.BlobsAcquireLeaseResponse; -import com.azure.storage.blob.models.BlobsBreakLeaseResponse; -import com.azure.storage.blob.models.BlobsChangeLeaseResponse; -import com.azure.storage.blob.models.BlobsCopyFromURLResponse; -import com.azure.storage.blob.models.BlobsCreateSnapshotResponse; -import com.azure.storage.blob.models.BlobsDeleteResponse; -import com.azure.storage.blob.models.BlobsGetAccountInfoResponse; -import com.azure.storage.blob.models.BlobsGetPropertiesResponse; -import com.azure.storage.blob.models.BlobsReleaseLeaseResponse; -import com.azure.storage.blob.models.BlobsRenewLeaseResponse; -import com.azure.storage.blob.models.BlobsSetHTTPHeadersResponse; -import com.azure.storage.blob.models.BlobsSetMetadataResponse; -import com.azure.storage.blob.models.BlobsSetTierResponse; -import com.azure.storage.blob.models.BlobsStartCopyFromURLResponse; -import com.azure.storage.blob.models.BlobsUndeleteResponse; -import com.azure.storage.blob.models.DeleteSnapshotsOptionType; -import com.azure.storage.blob.models.LeaseAccessConditions; -import com.azure.storage.blob.models.Metadata; -import com.azure.storage.blob.models.ModifiedAccessConditions; -import com.azure.storage.blob.models.ReliableDownloadOptions; -import com.azure.storage.blob.models.SourceModifiedAccessConditions; -import reactor.core.publisher.Mono; - -import java.net.URL; - -import static com.azure.storage.blob.Utility.postProcessResponse; - -/** - * Represents a URL to a blob of any type: block, append, or page. It may be obtained by direct construction or via the - * create method on a {@link ContainerAsyncClient} object. This class does not hold any state about a particular blob but is - * instead a convenient way of sending off appropriate requests to the resource on the service. Please refer to the - * Azure Docs for more information. - */ -class BlobAsyncRawClient { - - protected AzureBlobStorageImpl azureBlobStorage; - - final String snapshot; - - /** - * Creates a {@code BlobAsyncRawClient} object pointing to the account specified by the URL and using the provided pipeline to - * make HTTP requests.. - */ - BlobAsyncRawClient(AzureBlobStorageImpl azureBlobStorage, String snapshot) { - this.azureBlobStorage = azureBlobStorage; - this.snapshot = snapshot; - } - - /** - * Copies the data at the source URL to a blob. For more information, see the Azure Docs - * - * @param sourceURL - * The source URL to copy from. URLs outside of Azure may only be copied to block blobs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=start_copy "Sample code for BlobAsyncRawClient.startCopyFromURL")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=start_copy_helper "Helper for start_copy sample.")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono startCopyFromURL(URL sourceURL) { - return this.startCopyFromURL(sourceURL, null, null, null); - } - - /** - * Copies the data at the source URL to a blob. For more information, see the Azure Docs - * - * @param sourceURL - * The source URL to copy from. URLs outside of Azure may only be copied to block blobs. - * @param metadata - * {@link Metadata} - * @param sourceModifiedAccessConditions - * {@link ModifiedAccessConditions} against the source. Standard HTTP Access conditions related to the - * modification of data. ETag and LastModifiedTime are used to construct conditions related to when the blob - * was changed relative to the given request. The request will fail if the specified condition is not - * satisfied. - * @param destAccessConditions - * {@link BlobAccessConditions} against the destination. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=start_copy "Sample code for BlobAsyncRawClient.startCopyFromURL")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=start_copy_helper "Helper for start_copy sample.")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono startCopyFromURL(URL sourceURL, Metadata metadata, - ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - sourceModifiedAccessConditions = sourceModifiedAccessConditions == null - ? new ModifiedAccessConditions() : sourceModifiedAccessConditions; - destAccessConditions = destAccessConditions == null ? new BlobAccessConditions() : destAccessConditions; - - // We want to hide the SourceAccessConditions type from the user for consistency's sake, so we convert here. - SourceModifiedAccessConditions sourceConditions = new SourceModifiedAccessConditions() - .sourceIfModifiedSince(sourceModifiedAccessConditions.ifModifiedSince()) - .sourceIfUnmodifiedSince(sourceModifiedAccessConditions.ifUnmodifiedSince()) - .sourceIfMatch(sourceModifiedAccessConditions.ifMatch()) - .sourceIfNoneMatch(sourceModifiedAccessConditions.ifNoneMatch()); - - return postProcessResponse(this.azureBlobStorage.blobs().startCopyFromURLWithRestResponseAsync( - null, null, sourceURL, null, metadata, null, sourceConditions, - destAccessConditions.modifiedAccessConditions(), destAccessConditions.leaseAccessConditions(), Context.NONE)); - } - - /** - * Stops a pending copy that was previously started and leaves a destination blob with 0 length and metadata. For - * more information, see the Azure Docs. - * - * @param copyId - * The id of the copy operation to abort. Returned as the {@code copyId} field on the {@link - * BlobStartCopyFromURLHeaders} object. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=abort_copy "Sample code for BlobAsyncRawClient.abortCopyFromURL")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono abortCopyFromURL(String copyId) { - return this.abortCopyFromURL(copyId, null); - } - - /** - * Stops a pending copy that was previously started and leaves a destination blob with 0 length and metadata. For - * more information, see the Azure Docs. - * - * @param copyId - * The id of the copy operation to abort. Returned as the {@code copyId} field on the {@link - * BlobStartCopyFromURLHeaders} object. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=abort_copy "Sample code for BlobAsyncRawClient.abortCopyFromURL")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono abortCopyFromURL(String copyId, - LeaseAccessConditions leaseAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blobs().abortCopyFromURLWithRestResponseAsync( - null, null, copyId, null, null, leaseAccessConditions, Context.NONE)); - } - - /** - * Copies the data at the source URL to a blob and waits for the copy to complete before returning a response. - * For more information, see the Azure Docs - * - * @param copySource - * The source URL to copy from. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=sync_copy "Sample code for BlobAsyncRawClient.copyFromURL")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono syncCopyFromURL(URL copySource) { - return this.syncCopyFromURL(copySource, null, null, null); - } - - /** - * Copies the data at the source URL to a blob and waits for the copy to complete before returning a response. - * For more information, see the Azure Docs - * - * @param copySource - * The source URL to copy from. URLs outside of Azure may only be copied to block blobs. - * @param metadata - * {@link Metadata} - * @param sourceModifiedAccessConditions - * {@link ModifiedAccessConditions} against the source. Standard HTTP Access conditions related to the - * modification of data. ETag and LastModifiedTime are used to construct conditions related to when the blob - * was changed relative to the given request. The request will fail if the specified condition is not - * satisfied. - * @param destAccessConditions - * {@link BlobAccessConditions} against the destination. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=sync_copy "Sample code for BlobAsyncRawClient.copyFromURL")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono syncCopyFromURL(URL copySource, Metadata metadata, - ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - sourceModifiedAccessConditions = sourceModifiedAccessConditions == null - ? new ModifiedAccessConditions() : sourceModifiedAccessConditions; - destAccessConditions = destAccessConditions == null ? new BlobAccessConditions() : destAccessConditions; - - // We want to hide the SourceAccessConditions type from the user for consistency's sake, so we convert here. - SourceModifiedAccessConditions sourceConditions = new SourceModifiedAccessConditions() - .sourceIfModifiedSince(sourceModifiedAccessConditions.ifModifiedSince()) - .sourceIfUnmodifiedSince(sourceModifiedAccessConditions.ifUnmodifiedSince()) - .sourceIfMatch(sourceModifiedAccessConditions.ifMatch()) - .sourceIfNoneMatch(sourceModifiedAccessConditions.ifNoneMatch()); - - return postProcessResponse(this.azureBlobStorage.blobs().copyFromURLWithRestResponseAsync( - null, null, copySource, null, metadata, null, sourceConditions, - destAccessConditions.modifiedAccessConditions(), destAccessConditions.leaseAccessConditions(), Context.NONE)); - } - - /** - * Reads a range of bytes from a blob. The response also includes the blob's properties and metadata. For more - * information, see the Azure Docs. - *

- * Note that the response body has reliable download functionality built in, meaning that a failed download stream - * will be automatically retried. This behavior may be configured with {@link ReliableDownloadOptions}. - * - * @return Emits the successful response. - * @apiNote ## Sample Code \n [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=upload_download - * "Sample code for BlobAsyncRawClient.download")] \n For more samples, please see the [Samples - * file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono download() { - return this.download(null, null, false); - } - - /** - * Reads a range of bytes from a blob. The response also includes the blob's properties and metadata. For more - * information, see the Azure Docs. - *

- * Note that the response body has reliable download functionality built in, meaning that a failed download stream - * will be automatically retried. This behavior may be configured with {@link ReliableDownloadOptions}. - * - * @param range - * {@link BlobRange} - * @param accessConditions - * {@link BlobAccessConditions} - * @param rangeGetContentMD5 - * Whether the contentMD5 for the specified blob range should be returned. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=upload_download "Sample code for BlobAsyncRawClient.download")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono download(BlobRange range, BlobAccessConditions accessConditions, - boolean rangeGetContentMD5) { - range = range == null ? new BlobRange(0) : range; - Boolean getMD5 = rangeGetContentMD5 ? rangeGetContentMD5 : null; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - HTTPGetterInfo info = new HTTPGetterInfo() - .offset(range.offset()) - .count(range.count()) - .eTag(accessConditions.modifiedAccessConditions().ifMatch()); - - // TODO: range is BlobRange but expected as String - // TODO: figure out correct response - return postProcessResponse(this.azureBlobStorage.blobs().downloadWithRestResponseAsync( - null, null, snapshot, null, null, range.toHeaderValue(), getMD5, - null, null, null, null, - accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) - // Convert the autorest response to a DownloadAsyncResponse, which enable reliable download. - .map(response -> { - // If there wasn't an etag originally specified, lock on the one returned. - info.eTag(response.deserializedHeaders().eTag()); - return new DownloadAsyncResponse(response, info, - // In the event of a stream failure, make a new request to pick up where we left off. - newInfo -> - this.download(new BlobRange(newInfo.offset(), newInfo.count()), - new BlobAccessConditions().modifiedAccessConditions( - new ModifiedAccessConditions().ifMatch(info.eTag())), false)); - }); - } - - /** - * Deletes the specified blob or snapshot. Note that deleting a blob also deletes all its snapshots. For more - * information, see the Azure Docs. - * - * @return Emits the successful response. - * @apiNote ## Sample Code \n [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_delete - * "Sample code for BlobAsyncRawClient.delete")] \n For more samples, please see the [Samples - * file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono delete() { - return this.delete(null, null); - } - - /** - * Deletes the specified blob or snapshot. Note that deleting a blob also deletes all its snapshots. For more - * information, see the Azure Docs. - * - * @param deleteBlobSnapshotOptions - * Specifies the behavior for deleting the snapshots on this blob. {@code Include} will delete the base blob - * and all snapshots. {@code Only} will delete only the snapshots. If a snapshot is being deleted, you must - * pass null. - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_delete "Sample code for BlobAsyncRawClient.delete")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono delete(DeleteSnapshotsOptionType deleteBlobSnapshotOptions, - BlobAccessConditions accessConditions) { - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blobs().deleteWithRestResponseAsync( - null, null, snapshot, null, null, deleteBlobSnapshotOptions, - null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), - Context.NONE)); - } - - /** - * Returns the blob's metadata and properties. For more information, see the Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=properties_metadata "Sample code for BlobAsyncRawClient.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getProperties() { - return this.getProperties(null); - } - - /** - * Returns the blob's metadata and properties. For more information, see the Azure Docs. - * - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=properties_metadata "Sample code for BlobAsyncRawClient.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getProperties(BlobAccessConditions accessConditions) { - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blobs().getPropertiesWithRestResponseAsync( - null, null, snapshot, null, null, null, - null, null, null, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Changes a blob's HTTP header properties. For more information, see the Azure - * Docs. - * - * @param headers - * {@link BlobHTTPHeaders} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=properties_metadata "Sample code for BlobAsyncRawClient.setHTTPHeaders")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setHTTPHeaders(BlobHTTPHeaders headers) { - return this.setHTTPHeaders(headers, null); - } - - /** - * Changes a blob's HTTP header properties. For more information, see the Azure Docs. - * - * @param headers - * {@link BlobHTTPHeaders} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=properties_metadata "Sample code for BlobAsyncRawClient.setHTTPHeaders")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setHTTPHeaders(BlobHTTPHeaders headers, - BlobAccessConditions accessConditions) { - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blobs().setHTTPHeadersWithRestResponseAsync( - null, null, null, null, headers, - accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Changes a blob's metadata. For more information, see the Azure Docs. - * - * @param metadata - * {@link Metadata} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=properties_metadata "Sample code for BlobAsyncRawClient.setMetadata")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setMetadata(Metadata metadata) { - return this.setMetadata(metadata, null); - } - - /** - * Changes a blob's metadata. For more information, see the Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=properties_metadata "Sample code for BlobAsyncRawClient.setMetadata")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setMetadata(Metadata metadata, BlobAccessConditions accessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blobs().setMetadataWithRestResponseAsync( - null, null, null, metadata, null, null, - null, null, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Creates a read-only snapshot of a blob. For more information, see the Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=snapshot "Sample code for BlobAsyncRawClient.createSnapshot")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono createSnapshot() { - return this.createSnapshot(null, null); - } - - /** - * Creates a read-only snapshot of a blob. For more information, see the Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=snapshot "Sample code for BlobAsyncRawClient.createSnapshot")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono createSnapshot(Metadata metadata, BlobAccessConditions accessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blobs().createSnapshotWithRestResponseAsync( - null, null, null, metadata, null, null, - null, null, accessConditions.modifiedAccessConditions(), - accessConditions.leaseAccessConditions(), Context.NONE)); - } - - /** - * Sets the tier on a blob. The operation is allowed on a page blob in a premium storage account or a block blob in - * a blob storage or GPV2 account. A premium page blob's tier determines the allowed size, IOPS, and bandwidth of - * the blob. A block blob's tier determines the Hot/Cool/Archive storage type. This does not update the blob's etag. - *

- * For detailed information about block blob level tiering see the Azure Docs. - * - * @param tier - * The new tier for the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=tier "Sample code for BlobAsyncRawClient.setTier")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setTier(AccessTier tier) { - return this.setTier(tier, null); - } - - /** - * Sets the tier on a blob. The operation is allowed on a page blob in a premium storage account or a block blob in - * a blob storage or GPV2 account. A premium page blob's tier determines the allowed size, IOPS, and bandwidth of - * the blob. A block blob's tier determines the Hot/Cool/Archive storage type. This does not update the blob's etag. - *

- * For detailed information about block blob level tiering see the Azure Docs. - * - * @param tier - * The new tier for the blob. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=tier "Sample code for BlobAsyncRawClient.setTier")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setTier(AccessTier tier, LeaseAccessConditions leaseAccessConditions) { - Utility.assertNotNull("tier", tier); - - return postProcessResponse(this.azureBlobStorage.blobs().setTierWithRestResponseAsync( - null, null, tier, null, null, leaseAccessConditions, Context.NONE)); - } - - /** - * Undelete restores the content and metadata of a soft-deleted blob and/or any associated soft-deleted snapshots. - * For more information, see the Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=undelete "Sample code for BlobAsyncRawClient.undelete")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono undelete() { - return postProcessResponse(this.azureBlobStorage.blobs().undeleteWithRestResponseAsync(null, - null, Context.NONE)); - } - - /** - * Acquires a lease on the blob for write and delete operations. The lease duration must be between 15 to 60 - * seconds, or infinite (-1). For more information, see the Azure Docs. - * - * @param proposedId - * A {@code String} in any valid GUID format. May be null. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that - * never expires. A non-infinite lease can be between 15 and 60 seconds. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono acquireLease(String proposedId, int duration) { - return this.acquireLease(proposedId, duration, null); - } - - /** - * Acquires a lease on the blob for write and delete operations. The lease duration must be between 15 to 60 - * seconds, or infinite (-1). For more information, see the Azure Docs. - * - * @param proposedID - * A {@code String} in any valid GUID format. May be null. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that - * never expires. A non-infinite lease can be between 15 and 60 seconds. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code duration} is outside the bounds of 15 to 60 or isn't -1. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono acquireLease(String proposedID, int duration, - ModifiedAccessConditions modifiedAccessConditions) { - if (!(duration == -1 || (duration >= 15 && duration <= 60))) { - // Throwing is preferred to Mono.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("Duration must be -1 or between 15 and 60."); - } - - return postProcessResponse(this.azureBlobStorage.blobs().acquireLeaseWithRestResponseAsync( - null, null, null, duration, proposedID, null, - modifiedAccessConditions, Context.NONE)); - } - - /** - * Renews the blob's previously-acquired lease. For more information, see the Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.renewLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono renewLease(String leaseID) { - return this.renewLease(leaseID, null); - } - - /** - * Renews the blob's previously-acquired lease. For more information, see the Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the blob. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.renewLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono renewLease(String leaseID, ModifiedAccessConditions modifiedAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blobs().renewLeaseWithRestResponseAsync(null, - null, leaseID, null, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * Releases the blob's previously-acquired lease. For more information, see the Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.releaseLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono releaseLease(String leaseID) { - return this.releaseLease(leaseID, null); - } - - /** - * Releases the blob's previously-acquired lease. For more information, see the Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the blob. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.releaseLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono releaseLease(String leaseID, - ModifiedAccessConditions modifiedAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blobs().releaseLeaseWithRestResponseAsync(null, - null, leaseID, null, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1) constant - * to break a fixed-duration lease when it expires or an infinite lease immediately. For more information, see the - * Azure Docs. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.breakLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - * - * @return - * Emits the successful response. - */ - public Mono breakLease() { - return this.breakLease(null, null); - } - - /** - * BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1) constant - * to break a fixed-duration lease when it expires or an infinite lease immediately. For more information, see the - * Azure Docs. - * - * @param breakPeriodInSeconds - * An optional {@code Integer} representing the proposed duration of seconds that the lease should continue - * before it is broken, between 0 and 60 seconds. This break period is only used if it is shorter than the - * time remaining on the lease. If longer, the time remaining on the lease is used. A new lease will not be - * available before the break period has expired, but the lease may be held for longer than the break - * period. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.breakLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono breakLease(Integer breakPeriodInSeconds, - ModifiedAccessConditions modifiedAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blobs().breakLeaseWithRestResponseAsync(null, - null, null, breakPeriodInSeconds, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * ChangeLease changes the blob's lease ID. For more information, see the Azure Docs. - * - * @param leaseId - * The leaseId of the active lease on the blob. - * @param proposedID - * A {@code String} in any valid GUID format. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.changeLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono changeLease(String leaseId, String proposedID) { - return this.changeLease(leaseId, proposedID, null); - } - - /** - * ChangeLease changes the blob's lease ID. For more information, see the Azure Docs. - * - * @param leaseId - * The leaseId of the active lease on the blob. - * @param proposedID - * A {@code String} in any valid GUID format. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.changeLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono changeLease(String leaseId, String proposedID, - ModifiedAccessConditions modifiedAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blobs().changeLeaseWithRestResponseAsync(null, - null, leaseId, proposedID, null, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for BlobAsyncRawClient.getAccountInfo")] \n - * For more samples, please see the [Samples file](https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getAccountInfo() { - return postProcessResponse( - this.azureBlobStorage.blobs().getAccountInfoWithRestResponseAsync(null, null, Context.NONE)); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClient.java index f47c3cf6d7830..106b84729a39d 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClient.java @@ -44,7 +44,6 @@ * for more information. */ public class BlobClient { - private static final int BLOB_DEFAULT_DOWNLOAD_BLOCK_SIZE = 4 * Constants.MB; private final BlobAsyncClient blobAsyncClient; /** @@ -339,7 +338,7 @@ public VoidResponse download(OutputStream stream, ReliableDownloadOptions option * @throws IOException If an I/O error occurs */ public void downloadToFile(String filePath) throws IOException { - this.downloadToFile(filePath, null, null, BLOB_DEFAULT_DOWNLOAD_BLOCK_SIZE, null, false, null); + blobAsyncClient.downloadToFile(filePath); } /** diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobInputStream.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobInputStream.java index 2e093f50738f2..1a990e179688b 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobInputStream.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobInputStream.java @@ -189,7 +189,7 @@ public synchronized void close() throws IOException { */ private synchronized void dispatchRead(final int readLength) throws IOException { try { - this.currentBuffer = this.blobClient.blobAsyncRawClient.download(new BlobRange(this.currentAbsoluteReadPosition, (long) readLength), this.accessCondition, false) + this.currentBuffer = this.blobClient.download(new BlobRange(this.currentAbsoluteReadPosition, (long) readLength), this.accessCondition, false) .flatMap(res -> ByteBufFlux.fromInbound(res.body(null)).aggregate().asByteBuffer()) .block(); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobOutputStream.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobOutputStream.java index 176fe06c321d1..c733a580f2500 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobOutputStream.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobOutputStream.java @@ -295,7 +295,7 @@ private Mono writePages(Flux pageData, long offset, long writeLen PageBlobAccessConditions pageBlobAccessConditions = accessCondition == null ? null : new PageBlobAccessConditions().leaseAccessConditions(accessCondition.leaseAccessConditions()).modifiedAccessConditions(accessCondition.modifiedAccessConditions()); - return blobRef.pageBlobAsyncRawClient.uploadPages(new PageRange().start(offset).end(offset + writeLength - 1), pageData, pageBlobAccessConditions) + return blobRef.uploadPages(new PageRange().start(offset).end(offset + writeLength - 1), pageData, pageBlobAccessConditions) .then() .onErrorResume(t -> t instanceof StorageException, e -> { this.lastError = new IOException(e); @@ -312,7 +312,7 @@ private Mono appendBlock(Flux blockData, long offset, long writeL this.appendPositionAccessConditions.appendPosition(offset); AppendBlobAccessConditions appendBlobAccessConditions = accessCondition == null ? null : new AppendBlobAccessConditions().leaseAccessConditions(accessCondition.leaseAccessConditions()).modifiedAccessConditions(accessCondition.modifiedAccessConditions()); - return blobRef.appendBlobAsyncRawClient.appendBlock(blockData, writeLength, appendBlobAccessConditions) + return blobRef.appendBlock(blockData, writeLength, appendBlobAccessConditions) .then() .onErrorResume(t -> t instanceof IOException || t instanceof StorageException, e -> { this.lastError = new IOException(e); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobURLParts.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobURLParts.java index 473d998c446b2..1200e960f7501 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobURLParts.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobURLParts.java @@ -76,14 +76,14 @@ public BlobURLParts host(String host) { } /** - * The container name or {@code null} if a {@link StorageAsyncRawClient} was parsed. + * The container name or {@code null} if a {@link StorageAsyncClient} was parsed. */ public String containerName() { return containerName; } /** - * The container name or {@code null} if a {@link StorageAsyncRawClient} was parsed. + * The container name or {@code null} if a {@link StorageAsyncClient} was parsed. */ public BlobURLParts containerName(String containerName) { this.containerName = containerName; @@ -91,14 +91,14 @@ public BlobURLParts containerName(String containerName) { } /** - * The blob name or {@code null} if a {@link StorageAsyncRawClient} or {@link ContainerAsyncClient} was parsed. + * The blob name or {@code null} if a {@link StorageAsyncClient} or {@link ContainerAsyncClient} was parsed. */ public String blobName() { return blobName; } /** - * The blob name or {@code null} if a {@link StorageAsyncRawClient} or {@link ContainerAsyncClient} was parsed. + * The blob name or {@code null} if a {@link StorageAsyncClient} or {@link ContainerAsyncClient} was parsed. */ public BlobURLParts blobName(String blobName) { this.blobName = blobName; diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncClient.java index ada7a125d4ef1..e7d8aa9b0c03d 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncClient.java @@ -8,6 +8,7 @@ import com.azure.core.http.rest.SimpleResponse; import com.azure.core.http.rest.VoidResponse; import com.azure.core.implementation.util.FluxUtil; +import com.azure.core.util.Context; import com.azure.storage.blob.implementation.AzureBlobStorageBuilder; import com.azure.storage.blob.models.BlobAccessConditions; import com.azure.storage.blob.models.BlobHTTPHeaders; @@ -15,11 +16,11 @@ import com.azure.storage.blob.models.BlockBlobItem; import com.azure.storage.blob.models.BlockItem; import com.azure.storage.blob.models.BlockListType; +import com.azure.storage.blob.models.BlockLookupList; import com.azure.storage.blob.models.LeaseAccessConditions; import com.azure.storage.blob.models.Metadata; import com.azure.storage.blob.models.SourceModifiedAccessConditions; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -27,7 +28,6 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.net.URL; -import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; @@ -39,6 +39,8 @@ import java.util.TreeMap; import java.util.UUID; +import static com.azure.storage.blob.Utility.postProcessResponse; + /** * Client to a block blob. It may only be instantiated through a {@link BlockBlobClientBuilder}, via * the method {@link BlobAsyncClient#asBlockBlobAsyncClient()}, or via the method @@ -66,8 +68,6 @@ public final class BlockBlobAsyncClient extends BlobAsyncClient { static final int BLOB_DEFAULT_UPLOAD_BLOCK_SIZE = 4 * Constants.MB; static final int BLOB_MAX_UPLOAD_BLOCK_SIZE = 100 * Constants.MB; - final BlockBlobAsyncRawClient blockBlobAsyncRawClient; - /** * Indicates the maximum number of bytes that can be sent in a call to upload. */ @@ -89,7 +89,6 @@ public final class BlockBlobAsyncClient extends BlobAsyncClient { */ BlockBlobAsyncClient(AzureBlobStorageBuilder azureBlobStorageBuilder, String snapshot) { super(azureBlobStorageBuilder, snapshot); - this.blockBlobAsyncRawClient = new BlockBlobAsyncRawClient(azureBlobStorageBuilder.build(), snapshot); } /** @@ -114,7 +113,7 @@ public final class BlockBlobAsyncClient extends BlobAsyncClient { * @return * A reactive response containing the information of the uploaded block blob. */ - public Mono> upload(Flux data, long length) { + public Mono> upload(Flux data, long length) { return this.upload(data, length, null, null, null); } @@ -146,10 +145,15 @@ public Mono> upload(Flux data, long length) * @return * A reactive response containing the information of the uploaded block blob. */ - public Mono> upload(Flux data, long length, BlobHTTPHeaders headers, + public Mono> upload(Flux data, long length, BlobHTTPHeaders headers, Metadata metadata, BlobAccessConditions accessConditions) { - return blockBlobAsyncRawClient - .upload(data.map(Unpooled::wrappedBuffer), length, headers, metadata, accessConditions) + metadata = metadata == null ? new Metadata() : metadata; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blockBlobs().uploadWithRestResponseAsync(null, + null, data, length, null, metadata, null, null, + null, null, headers, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new BlockBlobItem(rb.deserializedHeaders()))); } @@ -291,8 +295,9 @@ public Mono stageBlock(String base64BlockID, Flux data, */ public Mono stageBlock(String base64BlockID, Flux data, long length, LeaseAccessConditions leaseAccessConditions) { - return blockBlobAsyncRawClient - .stageBlock(base64BlockID, data, length, leaseAccessConditions) + return postProcessResponse(this.azureBlobStorage.blockBlobs().stageBlockWithRestResponseAsync(null, + null, base64BlockID, length, data, null, null, null, + null, null, null, leaseAccessConditions, Context.NONE)) .map(VoidResponse::new); } @@ -349,8 +354,13 @@ public Mono stageBlockFromURL(String base64BlockID, URL sourceURL, public Mono stageBlockFromURL(String base64BlockID, URL sourceURL, BlobRange sourceRange, byte[] sourceContentMD5, LeaseAccessConditions leaseAccessConditions, SourceModifiedAccessConditions sourceModifiedAccessConditions) { - return blockBlobAsyncRawClient - .stageBlockFromURL(base64BlockID, sourceURL, sourceRange, sourceContentMD5, leaseAccessConditions, sourceModifiedAccessConditions) + sourceRange = sourceRange == null ? new BlobRange(0) : sourceRange; + + return postProcessResponse( + this.azureBlobStorage.blockBlobs().stageBlockFromURLWithRestResponseAsync(null, null, + base64BlockID, 0, sourceURL, sourceRange.toHeaderValue(), sourceContentMD5, null, + null, null, null, null, + leaseAccessConditions, sourceModifiedAccessConditions, Context.NONE)) .map(VoidResponse::new); } @@ -386,8 +396,9 @@ public Flux listBlocks(BlockListType listType) { */ public Flux listBlocks(BlockListType listType, LeaseAccessConditions leaseAccessConditions) { - return blockBlobAsyncRawClient - .listBlocks(listType, leaseAccessConditions) + return postProcessResponse(this.azureBlobStorage.blockBlobs().getBlockListWithRestResponseAsync( + null, null, listType, snapshot, null, null, null, + leaseAccessConditions, Context.NONE)) .map(ResponseBase::value) .flatMapMany(bl -> { Flux committed = Flux.fromIterable(bl.committedBlocks()) @@ -440,8 +451,13 @@ public Mono> commitBlockList(List base64BlockIDs */ public Mono> commitBlockList(List base64BlockIDs, BlobHTTPHeaders headers, Metadata metadata, BlobAccessConditions accessConditions) { - return blockBlobAsyncRawClient - .commitBlockList(base64BlockIDs, headers, metadata, accessConditions) + metadata = metadata == null ? new Metadata() : metadata; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blockBlobs().commitBlockListWithRestResponseAsync( + null, null, new BlockLookupList().latest(base64BlockIDs), null, metadata, + null, null, null, null, headers, + accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new BlockBlobItem(rb.deserializedHeaders()))); } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncRawClient.java deleted file mode 100644 index 03b7e650167b6..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncRawClient.java +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.util.Context; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.BlobAccessConditions; -import com.azure.storage.blob.models.BlobHTTPHeaders; -import com.azure.storage.blob.models.BlobRange; -import com.azure.storage.blob.models.BlockBlobsCommitBlockListResponse; -import com.azure.storage.blob.models.BlockBlobsGetBlockListResponse; -import com.azure.storage.blob.models.BlockBlobsStageBlockFromURLResponse; -import com.azure.storage.blob.models.BlockBlobsStageBlockResponse; -import com.azure.storage.blob.models.BlockBlobsUploadResponse; -import com.azure.storage.blob.models.BlockListType; -import com.azure.storage.blob.models.BlockLookupList; -import com.azure.storage.blob.models.LeaseAccessConditions; -import com.azure.storage.blob.models.Metadata; -import com.azure.storage.blob.models.SourceModifiedAccessConditions; -import io.netty.buffer.ByteBuf; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.net.URL; -import java.util.List; - -import static com.azure.storage.blob.Utility.postProcessResponse; - -/** - * Represents a URL to a block blob. It may be obtained by direct construction or via the create method on a - * {@link ContainerAsyncClient} object. This class does not hold any state about a particular blob but is instead a convenient - * way of sending off appropriate requests to the resource on the service. Please refer to the - * Azure Docs - * for more information on block blobs. - */ -final class BlockBlobAsyncRawClient extends BlobAsyncRawClient { - - /** - * Indicates the maximum number of bytes that can be sent in a call to upload. - */ - public static final int MAX_UPLOAD_BLOB_BYTES = 256 * Constants.MB; - - /** - * Indicates the maximum number of bytes that can be sent in a call to stageBlock. - */ - public static final int MAX_STAGE_BLOCK_BYTES = 100 * Constants.MB; - - /** - * Indicates the maximum number of blocks allowed in a block blob. - */ - public static final int MAX_BLOCKS = 50000; - - /** - * Creates a {@code BlockBlobAsyncRawClient} object pointing to the account specified by the URL and using the provided - */ - BlockBlobAsyncRawClient(AzureBlobStorageImpl azureBlobStorage, String snapshot) { - super(azureBlobStorage, snapshot); - } - - - /** - * Creates a new block blob, or updates the content of an existing block blob. - * Updating an existing block blob overwrites any existing metadata on the blob. Partial updates are not - * supported with PutBlob; the content of the existing blob is overwritten with the new content. To - * perform a partial update of a block blob's, use PutBlock and PutBlockList. - * For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - *

- * - * @param data - * The data to write to the blob. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param length - * The exact length of the data. It is important that this value match precisely the length of the data - * emitted by the {@code Flux}. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=upload_download "Sample code for BlockBlobAsyncRawClient.upload")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono upload(Flux data, long length) { - return this.upload(data, length, null, null, null); - } - - /** - * Creates a new block blob, or updates the content of an existing block blob. - * Updating an existing block blob overwrites any existing metadata on the blob. Partial updates are not - * supported with PutBlob; the content of the existing blob is overwritten with the new content. To - * perform a partial update of a block blob's, use PutBlock and PutBlockList. - * For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - *

- * - * @param data - * The data to write to the blob. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param length - * The exact length of the data. It is important that this value match precisely the length of the data - * emitted by the {@code Flux}. - * @param headers - * {@link BlobHTTPHeaders} - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=upload_download "Sample code for BlockBlobAsyncRawClient.upload")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono upload(Flux data, long length, BlobHTTPHeaders headers, - Metadata metadata, BlobAccessConditions accessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blockBlobs().uploadWithRestResponseAsync(null, - null, data, length, null, metadata, null, null, - null, null, headers, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Uploads the specified block to the block blob's "staging area" to be later committed by a call to - * commitBlockList. For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - * - * @param base64BlockID - * A Base64 encoded {@code String} that specifies the ID for this block. Note that all block ids for a given - * blob must be the same length. - * @param data - * The data to write to the block. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param length - * The exact length of the data. It is important that this value match precisely the length of the data - * emitted by the {@code Flux}. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blocks "Sample code for BlockBlobAsyncRawClient.stageBlock")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono stageBlock(String base64BlockID, Flux data, - long length) { - return this.stageBlock(base64BlockID, data, length, null); - } - - /** - * Uploads the specified block to the block blob's "staging area" to be later committed by a call to - * commitBlockList. For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - * - * @param base64BlockID - * A Base64 encoded {@code String} that specifies the ID for this block. Note that all block ids for a given - * blob must be the same length. - * @param data - * The data to write to the block. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param length - * The exact length of the data. It is important that this value match precisely the length of the data - * emitted by the {@code Flux}. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blocks "Sample code for BlockBlobAsyncRawClient.stageBlock")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono stageBlock(String base64BlockID, Flux data, long length, - LeaseAccessConditions leaseAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blockBlobs().stageBlockWithRestResponseAsync(null, - null, base64BlockID, length, data, null, null, null, - null, null, null, leaseAccessConditions, Context.NONE)); - } - - /** - * Creates a new block to be committed as part of a blob where the contents are read from a URL. For more - * information, see the Azure Docs. - * - * @param base64BlockID - * A Base64 encoded {@code String} that specifies the ID for this block. Note that all block ids for a given - * blob must be the same length. - * @param sourceURL - * The url to the blob that will be the source of the copy. A source blob in the same storage account can be - * authenticated via Shared Key. However, if the source is a blob in another account, the source blob must - * either be public or must be authenticated via a shared access signature. If the source blob is public, no - * authentication is required to perform the operation. - * @param sourceRange - * {@link BlobRange} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=block_from_url "Sample code for BlockBlobAsyncRawClient.stageBlockFromURL")] - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono stageBlockFromURL(String base64BlockID, URL sourceURL, - BlobRange sourceRange) { - return this.stageBlockFromURL(base64BlockID, sourceURL, sourceRange, null, - null, null); - } - - /** - * Creates a new block to be committed as part of a blob where the contents are read from a URL. For more - * information, see the Azure Docs. - * - * @param base64BlockID - * A Base64 encoded {@code String} that specifies the ID for this block. Note that all block ids for a given - * blob must be the same length. - * @param sourceURL - * The url to the blob that will be the source of the copy. A source blob in the same storage account can - * be authenticated via Shared Key. However, if the source is a blob in another account, the source blob - * must either be public or must be authenticated via a shared access signature. If the source blob is - * public, no authentication is required to perform the operation. - * @param sourceRange - * {@link BlobRange} - * @param sourceContentMD5 - * An MD5 hash of the block content from the source blob. If specified, the service will calculate the MD5 - * of the received data and fail the request if it does not match the provided MD5. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * @param sourceModifiedAccessConditions - * {@link SourceModifiedAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=block_from_url "Sample code for BlockBlobAsyncRawClient.stageBlockFromURL")] - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono stageBlockFromURL(String base64BlockID, URL sourceURL, - BlobRange sourceRange, byte[] sourceContentMD5, LeaseAccessConditions leaseAccessConditions, - SourceModifiedAccessConditions sourceModifiedAccessConditions) { - sourceRange = sourceRange == null ? new BlobRange(0) : sourceRange; - - return postProcessResponse( - this.azureBlobStorage.blockBlobs().stageBlockFromURLWithRestResponseAsync(null, null, - base64BlockID, 0, sourceURL, sourceRange.toHeaderValue(), sourceContentMD5, null, - null, null, null, null, - leaseAccessConditions, sourceModifiedAccessConditions, Context.NONE)); - } - - /** - * Returns the list of blocks that have been uploaded as part of a block blob using the specified block list filter. - * For more information, see the - * Azure Docs. - * - * @param listType - * Specifies which type of blocks to return. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blocks "Sample code for BlockBlobAsyncRawClient.listBlocks")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono listBlocks(BlockListType listType) { - return this.listBlocks(listType, null); - } - - /** - * Returns the list of blocks that have been uploaded as part of a block blob using the specified block list filter. - * For more information, see the - * Azure Docs. - * - * @param listType - * Specifies which type of blocks to return. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blocks "Sample code for BlockBlobAsyncRawClient.listBlocks")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono listBlocks(BlockListType listType, - LeaseAccessConditions leaseAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blockBlobs().getBlockListWithRestResponseAsync( - null, null, listType, snapshot, null, null, null, - leaseAccessConditions, Context.NONE)); - } - - /** - * Writes a blob by specifying the list of block IDs that are to make up the blob. - * In order to be written as part of a blob, a block must have been successfully written - * to the server in a prior stageBlock operation. You can call commitBlockList to update a blob - * by uploading only those blocks that have changed, then committing the new and existing - * blocks together. Any blocks not specified in the block list and permanently deleted. - * For more information, see the - * Azure Docs. - *

- * - * @param base64BlockIDs - * A list of base64 encode {@code String}s that specifies the block IDs to be committed. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blocks "Sample code for BlockBlobAsyncRawClient.commitBlockList")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono commitBlockList(List base64BlockIDs) { - return this.commitBlockList(base64BlockIDs, null, null, null); - } - - /** - * Writes a blob by specifying the list of block IDs that are to make up the blob. - * In order to be written as part of a blob, a block must have been successfully written - * to the server in a prior stageBlock operation. You can call commitBlockList to update a blob - * by uploading only those blocks that have changed, then committing the new and existing - * blocks together. Any blocks not specified in the block list and permanently deleted. - * For more information, see the - * Azure Docs. - *

- * - * @param base64BlockIDs - * A list of base64 encode {@code String}s that specifies the block IDs to be committed. - * @param headers - * {@link BlobHTTPHeaders} - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blocks "Sample code for BlockBlobAsyncRawClient.commitBlockList")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono commitBlockList(List base64BlockIDs, - BlobHTTPHeaders headers, Metadata metadata, BlobAccessConditions accessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blockBlobs().commitBlockListWithRestResponseAsync( - null, null, new BlockLookupList().latest(base64BlockIDs), null, metadata, - null, null, null, null, headers, - accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobClient.java index 9dced1f87022b..ec76f684c1bb4 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobClient.java @@ -4,7 +4,6 @@ package com.azure.storage.blob; import com.azure.core.http.rest.Response; -import com.azure.core.http.rest.SimpleResponse; import com.azure.core.http.rest.VoidResponse; import com.azure.storage.blob.models.BlobAccessConditions; import com.azure.storage.blob.models.BlobHTTPHeaders; @@ -44,22 +43,22 @@ * for more information. */ public final class BlockBlobClient extends BlobClient { + private final BlockBlobAsyncClient blockBlobAsyncClient; - private BlockBlobAsyncClient blockBlobAsyncClient; /** * Indicates the maximum number of bytes that can be sent in a call to upload. */ - public static final int MAX_UPLOAD_BLOB_BYTES = 256 * Constants.MB; + public static final int MAX_UPLOAD_BLOB_BYTES = BlockBlobAsyncClient.MAX_UPLOAD_BLOB_BYTES; /** * Indicates the maximum number of bytes that can be sent in a call to stageBlock. */ - public static final int MAX_STAGE_BLOCK_BYTES = 100 * Constants.MB; + public static final int MAX_STAGE_BLOCK_BYTES = BlockBlobAsyncClient.MAX_STAGE_BLOCK_BYTES; /** * Indicates the maximum number of blocks allowed in a block blob. */ - public static final int MAX_BLOCKS = 50000; + public static final int MAX_BLOCKS = BlockBlobAsyncClient.MAX_BLOCKS; /** * Package-private constructor for use by {@link BlockBlobClientBuilder}. @@ -161,9 +160,8 @@ public Response upload(InputStream data, long length, BlobHTTPHea return ByteBufAllocator.DEFAULT.buffer((int) count).writeBytes(cache); })); - Mono> upload = blockBlobAsyncClient.blockBlobAsyncRawClient - .upload(fbb.subscribeOn(Schedulers.elastic()), length, headers, metadata, accessConditions) - .map(rb -> new SimpleResponse<>(rb, new BlockBlobItem(rb.deserializedHeaders()))); + Mono> upload = blockBlobAsyncClient + .upload(fbb.subscribeOn(Schedulers.elastic()), length, headers, metadata, accessConditions); try { return Utility.blockWithOptionalTimeout(upload, timeout); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncClient.java index 406a7440b0c5b..b61d2b36f3c31 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncClient.java @@ -9,6 +9,7 @@ import com.azure.core.http.rest.VoidResponse; import com.azure.core.util.Context; import com.azure.storage.blob.implementation.AzureBlobStorageBuilder; +import com.azure.storage.blob.implementation.AzureBlobStorageImpl; import com.azure.storage.blob.models.BlobFlatListSegment; import com.azure.storage.blob.models.BlobHierarchyListSegment; import com.azure.storage.blob.models.BlobItem; @@ -30,8 +31,11 @@ import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; +import java.time.temporal.ChronoUnit; import java.util.List; +import static com.azure.storage.blob.Utility.postProcessResponse; + /** * Client to a container. It may only be instantiated through a {@link ContainerClientBuilder} or via the method * {@link StorageAsyncClient#getContainerAsyncClient(String)}. This class does not hold any @@ -54,20 +58,20 @@ * object through {@link Mono#toFuture()}. */ public final class ContainerAsyncClient { - ContainerAsyncRawClient containerAsyncRawClient; - public static final String ROOT_CONTAINER_NAME = "$root"; public static final String STATIC_WEBSITE_CONTAINER_NAME = "$web"; public static final String LOG_CONTAINER_NAME = "$logs"; + private final AzureBlobStorageImpl azureBlobStorage; + /** * Package-private constructor for use by {@link ContainerClientBuilder}. * @param azureBlobStorageBuilder the API client builder for blob storage API */ ContainerAsyncClient(AzureBlobStorageBuilder azureBlobStorageBuilder) { - this.containerAsyncRawClient = new ContainerAsyncRawClient(azureBlobStorageBuilder.build()); + this.azureBlobStorage = azureBlobStorageBuilder.build(); } /** @@ -103,7 +107,7 @@ public BlockBlobAsyncClient getBlockBlobAsyncClient(String blobName) { public BlockBlobAsyncClient getBlockBlobAsyncClient(String blobName, String snapshot) { return new BlockBlobAsyncClient(new AzureBlobStorageBuilder() .url(Utility.appendToURLPath(getContainerUrl(), blobName).toString()) - .pipeline(containerAsyncRawClient.azureBlobStorage.httpPipeline()), snapshot); + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** @@ -139,7 +143,7 @@ public PageBlobAsyncClient getPageBlobAsyncClient(String blobName) { public PageBlobAsyncClient getPageBlobAsyncClient(String blobName, String snapshot) { return new PageBlobAsyncClient(new AzureBlobStorageBuilder() .url(Utility.appendToURLPath(getContainerUrl(), blobName).toString()) - .pipeline(containerAsyncRawClient.azureBlobStorage.httpPipeline()), snapshot); + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** @@ -175,7 +179,7 @@ public AppendBlobAsyncClient getAppendBlobAsyncClient(String blobName) { public AppendBlobAsyncClient getAppendBlobAsyncClient(String blobName, String snapshot) { return new AppendBlobAsyncClient(new AzureBlobStorageBuilder() .url(Utility.appendToURLPath(getContainerUrl(), blobName).toString()) - .pipeline(containerAsyncRawClient.azureBlobStorage.httpPipeline()), snapshot); + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** @@ -211,7 +215,7 @@ public BlobAsyncClient getBlobAsyncClient(String blobName) { public BlobAsyncClient getBlobAsyncClient(String blobName, String snapshot) { return new BlobAsyncClient(new AzureBlobStorageBuilder() .url(Utility.appendToURLPath(getContainerUrl(), blobName).toString()) - .pipeline(containerAsyncRawClient.azureBlobStorage.httpPipeline()), snapshot); + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** @@ -223,7 +227,7 @@ public BlobAsyncClient getBlobAsyncClient(String blobName, String snapshot) { public StorageAsyncClient getStorageAsyncClient() { return new StorageAsyncClient(new AzureBlobStorageBuilder() .url(Utility.stripLastPathSegment(getContainerUrl()).toString()) - .pipeline(containerAsyncRawClient.azureBlobStorage.httpPipeline())); + .pipeline(azureBlobStorage.httpPipeline())); } /** @@ -233,9 +237,9 @@ public StorageAsyncClient getStorageAsyncClient() { */ public URL getContainerUrl() { try { - return new URL(containerAsyncRawClient.azureBlobStorage.url()); + return new URL(azureBlobStorage.url()); } catch (MalformedURLException e) { - throw new RuntimeException(String.format("Invalid URL on %s: %s" + getClass().getSimpleName(), containerAsyncRawClient.azureBlobStorage.url()), e); + throw new RuntimeException(String.format("Invalid URL on %s: %s" + getClass().getSimpleName(), azureBlobStorage.url()), e); } } @@ -281,8 +285,10 @@ public Mono create() { * A reactive response signalling completion. */ public Mono create(Metadata metadata, PublicAccessType accessType) { - return containerAsyncRawClient - .create(metadata, accessType) + metadata = metadata == null ? new Metadata() : metadata; + + return postProcessResponse(this.azureBlobStorage.containers().createWithRestResponseAsync( + null, null, metadata, accessType, null, Context.NONE)) .map(VoidResponse::new); } @@ -303,15 +309,23 @@ public Mono delete() { * deleted during garbage collection. For more information, see the * Azure Docs. * - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return - * A reactive response signalling completion. + * @param accessConditions {@link ContainerAccessConditions} + * @return A reactive response signalling completion. + * @throws UnsupportedOperationException If {@link ContainerAccessConditions#modifiedAccessConditions()} has either + * {@link ModifiedAccessConditions#ifMatch()} or {@link ModifiedAccessConditions#ifNoneMatch()} set. */ public Mono delete(ContainerAccessConditions accessConditions) { - return containerAsyncRawClient - .delete(accessConditions) + accessConditions = accessConditions == null ? new ContainerAccessConditions() : accessConditions; + + if (!validateNoEtag(accessConditions.modifiedAccessConditions())) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException("ETag access conditions are not supported for this API."); + } + + return postProcessResponse(this.azureBlobStorage.containers() + .deleteWithRestResponseAsync(null, null, null, + accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) .map(VoidResponse::new); } @@ -338,8 +352,9 @@ public Mono> getProperties() { * A reactive response containing the container properties. */ public Mono> getProperties(LeaseAccessConditions leaseAccessConditions) { - return containerAsyncRawClient - .getProperties(leaseAccessConditions) + return postProcessResponse(this.azureBlobStorage.containers() + .getPropertiesWithRestResponseAsync(null, null, null, + leaseAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new ContainerProperties(rb.deserializedHeaders()))); } @@ -361,18 +376,27 @@ public Mono setMetadata(Metadata metadata) { * Sets the container's metadata. For more information, see the * Azure Docs. * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return - * A reactive response signalling completion. + * @param metadata {@link Metadata} + * @param accessConditions {@link ContainerAccessConditions} + * @return A reactive response signalling completion. + * @throws UnsupportedOperationException If {@link ContainerAccessConditions#modifiedAccessConditions()} has anything + * set other than {@link ModifiedAccessConditions#ifModifiedSince()}. */ public Mono setMetadata(Metadata metadata, ContainerAccessConditions accessConditions) { - return containerAsyncRawClient - .setMetadata(metadata, accessConditions) + metadata = metadata == null ? new Metadata() : metadata; + accessConditions = accessConditions == null ? new ContainerAccessConditions() : accessConditions; + if (!validateNoEtag(accessConditions.modifiedAccessConditions()) + || accessConditions.modifiedAccessConditions().ifUnmodifiedSince() != null) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException( + "If-Modified-Since is the only HTTP access condition supported for this API"); + } + + return postProcessResponse(this.azureBlobStorage.containers() + .setMetadataWithRestResponseAsync(null, null, metadata, null, + accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) .map(VoidResponse::new); } @@ -401,7 +425,8 @@ public Mono> getAccessPolicy() { * A reactive response containing the container access policy. */ public Mono> getAccessPolicy(LeaseAccessConditions leaseAccessConditions) { - return containerAsyncRawClient.getAccessPolicy(leaseAccessConditions); + return postProcessResponse(this.azureBlobStorage.containers().getAccessPolicyWithRestResponseAsync(null, null, null, leaseAccessConditions, Context.NONE) + .map(response -> new SimpleResponse<>(response, new ContainerAccessPolicies(response.deserializedHeaders().blobPublicAccess(), response.value())))); } /** @@ -442,13 +467,43 @@ public Mono setAccessPolicy(PublicAccessType accessType, * @param accessConditions * {@link ContainerAccessConditions} * - * @return - * A reactive response signalling completion. + * @return A reactive response signalling completion. + * @throws UnsupportedOperationException If {@link ContainerAccessConditions#modifiedAccessConditions()} has either + * {@link ModifiedAccessConditions#ifMatch()} or {@link ModifiedAccessConditions#ifNoneMatch()} set. */ public Mono setAccessPolicy(PublicAccessType accessType, List identifiers, ContainerAccessConditions accessConditions) { - return containerAsyncRawClient - .setAccessPolicy(accessType, identifiers, accessConditions) + accessConditions = accessConditions == null ? new ContainerAccessConditions() : accessConditions; + + if (!validateNoEtag(accessConditions.modifiedAccessConditions())) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException("ETag access conditions are not supported for this API."); + } + + /* + We truncate to seconds because the service only supports nanoseconds or seconds, but doing an + OffsetDateTime.now will only give back milliseconds (more precise fields are zeroed and not serialized). This + allows for proper serialization with no real detriment to users as sub-second precision on active time for + signed identifiers is not really necessary. + */ + if (identifiers != null) { + for (SignedIdentifier identifier : identifiers) { + if (identifier.accessPolicy() != null && identifier.accessPolicy().start() != null) { + identifier.accessPolicy().start( + identifier.accessPolicy().start().truncatedTo(ChronoUnit.SECONDS)); + } + if (identifier.accessPolicy() != null && identifier.accessPolicy().expiry() != null) { + identifier.accessPolicy().expiry( + identifier.accessPolicy().expiry().truncatedTo(ChronoUnit.SECONDS)); + } + } + } + + return postProcessResponse(this.azureBlobStorage.containers() + .setAccessPolicyWithRestResponseAsync(null, identifiers, null, accessType, + null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), + Context.NONE)) .map(VoidResponse::new); } @@ -502,9 +557,36 @@ public Flux listBlobsFlat() { * A reactive response emitting the listed blobs, flattened. */ public Flux listBlobsFlat(ListBlobsOptions options) { - return containerAsyncRawClient - .listBlobsFlatSegment(null, options) - .flatMapMany(response -> listBlobsFlatHelper(options, response)); + return listBlobsFlatSegment(null, options).flatMapMany(response -> listBlobsFlatHelper(options, response)); + } + + /* + * Returns a single segment of blobs starting from the specified Marker. Use an empty + * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. + * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned + * Marker) to get the next segment. For more information, see the + * Azure Docs. + * + * @param marker + * Identifies the portion of the list to be returned with the next list operation. + * This value is returned in the response of a previous list operation as the + * ListBlobsFlatSegmentResponse.body().nextMarker(). Set to null to list the first segment. + * @param options + * {@link ListBlobsOptions} + * + * @return Emits the successful response. + * + * @apiNote ## Sample Code \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat "Sample code for ContainerAsyncClient.listBlobsFlatSegment")] \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat_helper "helper code for ContainerAsyncClient.listBlobsFlatSegment")] \n + * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) + */ + private Mono listBlobsFlatSegment(String marker, ListBlobsOptions options) { + options = options == null ? new ListBlobsOptions() : options; + + return postProcessResponse(this.azureBlobStorage.containers() + .listBlobFlatSegmentWithRestResponseAsync(null, options.prefix(), marker, + options.maxResults(), options.details().toList(), null, null, Context.NONE)); } private Flux listBlobsFlatHelper(ListBlobsOptions options, ContainersListBlobFlatSegmentResponse response) { @@ -518,7 +600,7 @@ private Flux listBlobsFlatHelper(ListBlobsOptions options, ContainersL if (response.value().nextMarker() != null) { // Recursively add the continuation items to the observable. - result = result.concatWith(containerAsyncRawClient.listBlobsFlatSegment(response.value().nextMarker(), options) + result = result.concatWith(listBlobsFlatSegment(response.value().nextMarker(), options) .flatMapMany(r -> listBlobsFlatHelper(options, r))); } @@ -594,10 +676,48 @@ public Flux listBlobsHierarchy(String directory) { * A reactive response emitting the prefixes and blobs. */ public Flux listBlobsHierarchy(String delimiter, ListBlobsOptions options) { - return containerAsyncRawClient.listBlobsHierarchySegment(null, delimiter, options) + return listBlobsHierarchySegment(null, delimiter, options) .flatMapMany(response -> listBlobsHierarchyHelper(delimiter, options, Context.NONE, response)); } + /* + * Returns a single segment of blobs and blob prefixes starting from the specified Marker. Use an empty + * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. + * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned + * Marker) to get the next segment. For more information, see the + * Azure Docs. + * + * @param marker + * Identifies the portion of the list to be returned with the next list operation. + * This value is returned in the response of a previous list operation as the + * ListBlobsHierarchySegmentResponse.body().nextMarker(). Set to null to list the first segment. + * @param delimiter + * The operation returns a BlobPrefix element in the response body that acts as a placeholder for all blobs + * whose names begin with the same substring up to the appearance of the delimiter character. The delimiter may + * be a single character or a string. + * @param options + * {@link ListBlobsOptions} + * + * @return Emits the successful response. + * @throws UnsupportedOperationException If {@link ListBlobsOptions#details()} has {@link BlobListDetails#snapshots()} + * set. + * + * @apiNote ## Sample Code \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy "Sample code for ContainerAsyncClient.listBlobsHierarchySegment")] \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy_helper "helper code for ContainerAsyncClient.listBlobsHierarchySegment")] \n + * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) + */ + private Mono listBlobsHierarchySegment(String marker, String delimiter, ListBlobsOptions options) { + options = options == null ? new ListBlobsOptions() : options; + if (options.details().snapshots()) { + throw new UnsupportedOperationException("Including snapshots in a hierarchical listing is not supported."); + } + + return postProcessResponse(this.azureBlobStorage.containers() + .listBlobHierarchySegmentWithRestResponseAsync(null, delimiter, options.prefix(), marker, + options.maxResults(), options.details().toList(), null, null, Context.NONE)); + } + private Flux listBlobsHierarchyHelper(String delimiter, ListBlobsOptions options, Context context, ContainersListBlobHierarchySegmentResponse response) { Flux blobs; @@ -617,7 +737,7 @@ private Flux listBlobsHierarchyHelper(String delimiter, ListBlobsOptio if (response.value().nextMarker() != null) { // Recursively add the continuation items to the observable. - result = result.concatWith(containerAsyncRawClient.listBlobsHierarchySegment(response.value().nextMarker(), delimiter, options) + result = result.concatWith(listBlobsHierarchySegment(response.value().nextMarker(), delimiter, options) .flatMapMany(r -> listBlobsHierarchyHelper(delimiter, options, context, r))); } @@ -723,12 +843,20 @@ public Mono> acquireLease(String proposedId, int duration) { * to construct conditions related to when the blob was changed relative to the given request. The request * will fail if the specified condition is not satisfied. * - * @return - * A reactive response containing the lease ID. + * @return A reactive response containing the lease ID. + * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or + * {@link ModifiedAccessConditions#ifNoneMatch()} is set. */ public Mono> acquireLease(String proposedID, int duration, ModifiedAccessConditions modifiedAccessConditions) { - return containerAsyncRawClient - .acquireLease(proposedID, duration, modifiedAccessConditions) + if (!this.validateNoEtag(modifiedAccessConditions)) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException( + "ETag access conditions are not supported for this API."); + } + + return postProcessResponse(this.azureBlobStorage.containers().acquireLeaseWithRestResponseAsync( + null, null, duration, proposedID, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseId())); } @@ -755,12 +883,20 @@ public Mono> renewLease(String leaseID) { * to construct conditions related to when the blob was changed relative to the given request. The request * will fail if the specified condition is not satisfied. * - * @return - * A reactive response containing the renewed lease ID. + * @return A reactive response containing the renewed lease ID. + * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or + * {@link ModifiedAccessConditions#ifNoneMatch()} is set. */ public Mono> renewLease(String leaseID, ModifiedAccessConditions modifiedAccessConditions) { - return containerAsyncRawClient - .renewLease(leaseID, modifiedAccessConditions) + if (!this.validateNoEtag(modifiedAccessConditions)) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException( + "ETag access conditions are not supported for this API."); + } + + return postProcessResponse(this.azureBlobStorage.containers().renewLeaseWithRestResponseAsync(null, + leaseID, null, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseId())); } @@ -787,12 +923,20 @@ public Mono releaseLease(String leaseID) { * to construct conditions related to when the blob was changed relative to the given request. The request * will fail if the specified condition is not satisfied. * - * @return - * A reactive response signalling completion. + * @return A reactive response signalling completion. + * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or + * {@link ModifiedAccessConditions#ifNoneMatch()} is set. */ public Mono releaseLease(String leaseID, ModifiedAccessConditions modifiedAccessConditions) { - return containerAsyncRawClient - .releaseLease(leaseID, modifiedAccessConditions) + if (!this.validateNoEtag(modifiedAccessConditions)) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException( + "ETag access conditions are not supported for this API."); + } + + return postProcessResponse(this.azureBlobStorage.containers().releaseLeaseWithRestResponseAsync( + null, leaseID, null, null, modifiedAccessConditions, Context.NONE)) .map(VoidResponse::new); } @@ -822,12 +966,20 @@ public Mono> breakLease() { * to construct conditions related to when the blob was changed relative to the given request. The request * will fail if the specified condition is not satisfied. * - * @return - * A reactive response containing the remaining time in the broken lease. + * @return A reactive response containing the remaining time in the broken lease. + * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or + * {@link ModifiedAccessConditions#ifNoneMatch()} is set. */ public Mono> breakLease(Integer breakPeriodInSeconds, ModifiedAccessConditions modifiedAccessConditions) { - return containerAsyncRawClient - .breakLease(breakPeriodInSeconds, modifiedAccessConditions) + if (!this.validateNoEtag(modifiedAccessConditions)) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException( + "ETag access conditions are not supported for this API."); + } + + return postProcessResponse(this.azureBlobStorage.containers().breakLeaseWithRestResponseAsync(null, + null, breakPeriodInSeconds, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, Duration.ofSeconds(rb.deserializedHeaders().leaseTime()))); } @@ -859,10 +1011,19 @@ public Mono> changeLease(String leaseId, String proposedID) { * will fail if the specified condition is not satisfied. * * @return A reactive response containing the new lease ID. + * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or + * {@link ModifiedAccessConditions#ifNoneMatch()} is set. */ public Mono> changeLease(String leaseId, String proposedID, ModifiedAccessConditions modifiedAccessConditions) { - return containerAsyncRawClient - .changeLease(leaseId, proposedID, modifiedAccessConditions) + if (!this.validateNoEtag(modifiedAccessConditions)) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException( + "ETag access conditions are not supported for this API."); + } + + return postProcessResponse(this.azureBlobStorage.containers().changeLeaseWithRestResponseAsync(null, + leaseId, proposedID, null, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseId())); } @@ -874,8 +1035,15 @@ public Mono> changeLease(String leaseId, String proposedID, Mod * A reactive response containing the account info. */ public Mono> getAccountInfo() { - return containerAsyncRawClient - .getAccountInfo() + return postProcessResponse( + this.azureBlobStorage.containers().getAccountInfoWithRestResponseAsync(null, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new StorageAccountInfo(rb.deserializedHeaders()))); } + + private boolean validateNoEtag(ModifiedAccessConditions modifiedAccessConditions) { + if (modifiedAccessConditions == null) { + return true; + } + return modifiedAccessConditions.ifMatch() == null && modifiedAccessConditions.ifNoneMatch() == null; + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncRawClient.java deleted file mode 100644 index e4106549b208c..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncRawClient.java +++ /dev/null @@ -1,708 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.http.rest.Response; -import com.azure.core.http.rest.SimpleResponse; -import com.azure.core.util.Context; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.BlobListDetails; -import com.azure.storage.blob.models.ContainerAccessConditions; -import com.azure.storage.blob.models.ContainerAccessPolicies; -import com.azure.storage.blob.models.ContainersAcquireLeaseResponse; -import com.azure.storage.blob.models.ContainersBreakLeaseResponse; -import com.azure.storage.blob.models.ContainersChangeLeaseResponse; -import com.azure.storage.blob.models.ContainersCreateResponse; -import com.azure.storage.blob.models.ContainersDeleteResponse; -import com.azure.storage.blob.models.ContainersGetAccountInfoResponse; -import com.azure.storage.blob.models.ContainersGetPropertiesResponse; -import com.azure.storage.blob.models.ContainersListBlobFlatSegmentResponse; -import com.azure.storage.blob.models.ContainersListBlobHierarchySegmentResponse; -import com.azure.storage.blob.models.ContainersReleaseLeaseResponse; -import com.azure.storage.blob.models.ContainersRenewLeaseResponse; -import com.azure.storage.blob.models.ContainersSetAccessPolicyResponse; -import com.azure.storage.blob.models.ContainersSetMetadataResponse; -import com.azure.storage.blob.models.LeaseAccessConditions; -import com.azure.storage.blob.models.ListBlobsOptions; -import com.azure.storage.blob.models.Metadata; -import com.azure.storage.blob.models.ModifiedAccessConditions; -import com.azure.storage.blob.models.PublicAccessType; -import com.azure.storage.blob.models.SignedIdentifier; -import reactor.core.publisher.Mono; - -import java.time.temporal.ChronoUnit; -import java.util.List; - -import static com.azure.storage.blob.Utility.postProcessResponse; - -/** - * Represents a URL to a container. It may be obtained by direct construction or via the create method on a - * {@link StorageAsyncRawClient} object. This class does not hold any state about a particular blob but is instead a convenient way - * of sending off appropriate requests to the resource on the service. It may also be used to construct URLs to blobs. - * Please refer to the - * Azure Docs - * for more information on containers. - */ -final class ContainerAsyncRawClient { - - public static final String ROOT_CONTAINER_NAME = "$root"; - - public static final String STATIC_WEBSITE_CONTAINER_NAME = "$web"; - - public static final String LOG_CONTAINER_NAME = "$logs"; - - AzureBlobStorageImpl azureBlobStorage; - - /** - * Creates a {@code ContainerAsyncClient} object pointing to the account specified by the URL and using the provided - * pipeline to make HTTP requests. - */ - ContainerAsyncRawClient(AzureBlobStorageImpl azureBlobStorage) { - this.azureBlobStorage = azureBlobStorage; - } - - /** - * Creates a new container within a storage account. If a container with the same name already exists, the operation - * fails. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono create() { - return this.create(null, null); - } - - /** - * Creates a new container within a storage account. If a container with the same name already exists, the operation - * fails. For more information, see the - * Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono create(Metadata metadata, PublicAccessType accessType) { - metadata = metadata == null ? new Metadata() : metadata; - - return postProcessResponse(this.azureBlobStorage.containers().createWithRestResponseAsync( - null, null, metadata, accessType, null, Context.NONE)); - - } - - /** - * Marks the specified container for deletion. The container and any blobs contained within it are later - * deleted during garbage collection. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.delete")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono delete() { - return this.delete(null); - } - - /** - * Marks the specified container for deletion. The container and any blobs contained within it are later - * deleted during garbage collection. For more information, see the - * Azure Docs. - * - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If {@link ContainerAccessConditions#modifiedAccessConditions()} has either - * {@link ModifiedAccessConditions#ifMatch()} or {@link ModifiedAccessConditions#ifNoneMatch()} set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.delete")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono delete(ContainerAccessConditions accessConditions) { - accessConditions = accessConditions == null ? new ContainerAccessConditions() : accessConditions; - - if (!validateNoEtag(accessConditions.modifiedAccessConditions())) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException("ETag access conditions are not supported for this API."); - } - - return postProcessResponse(this.azureBlobStorage.containers() - .deleteWithRestResponseAsync(null, null, null, - accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Returns the container's metadata and system properties. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getProperties() { - return this.getProperties(null); - } - - /** - * Returns the container's metadata and system properties. For more information, see the - * Azure Docs. - * - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getProperties(LeaseAccessConditions leaseAccessConditions) { - return postProcessResponse(this.azureBlobStorage.containers() - .getPropertiesWithRestResponseAsync(null, null, null, - leaseAccessConditions, Context.NONE)); - } - - /** - * Sets the container's metadata. For more information, see the - * Azure Docs. - * - * @param metadata - * {@link Metadata} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.setMetadata")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setMetadata(Metadata metadata) { - return this.setMetadata(metadata, null); - } - - /** - * Sets the container's metadata. For more information, see the - * Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If {@link ContainerAccessConditions#modifiedAccessConditions()} has anything - * set other than {@link ModifiedAccessConditions#ifModifiedSince()}. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.setMetadata")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setMetadata(Metadata metadata, - ContainerAccessConditions accessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - accessConditions = accessConditions == null ? new ContainerAccessConditions() : accessConditions; - if (!validateNoEtag(accessConditions.modifiedAccessConditions()) - || accessConditions.modifiedAccessConditions().ifUnmodifiedSince() != null) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException( - "If-Modified-Since is the only HTTP access condition supported for this API"); - } - - return postProcessResponse(this.azureBlobStorage.containers() - .setMetadataWithRestResponseAsync(null, null, metadata, null, - accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Returns the container's permissions. The permissions indicate whether container's blobs may be accessed publicly. - * For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.getAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono> getAccessPolicy() { - return this.getAccessPolicy(null); - } - - /** - * Returns the container's permissions. The permissions indicate whether container's blobs may be accessed publicly. - * For more information, see the - * Azure Docs. - * - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.getAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono> getAccessPolicy(LeaseAccessConditions leaseAccessConditions) { - return postProcessResponse(this.azureBlobStorage.containers().getAccessPolicyWithRestResponseAsync(null, null, null, leaseAccessConditions, Context.NONE) - .map(response -> new SimpleResponse<>(response, new ContainerAccessPolicies(response.deserializedHeaders().blobPublicAccess(), response.value())))); - } - - /** - * Sets the container's permissions. The permissions indicate whether blobs in a container may be accessed publicly. - * Note that, for each signed identifier, we will truncate the start and expiry times to the nearest second to - * ensure the time formatting is compatible with the service. For more information, see the - * Azure Docs. - * - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param identifiers - * A list of {@link SignedIdentifier} objects that specify the permissions for the container. Please see - * here - * for more information. Passing null will clear all access policies. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.setAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setAccessPolicy(PublicAccessType accessType, - List identifiers) { - return this.setAccessPolicy(accessType, identifiers, null); - } - - /** - * Sets the container's permissions. The permissions indicate whether blobs in a container may be accessed publicly. - * Note that, for each signed identifier, we will truncate the start and expiry times to the nearest second to - * ensure the time formatting is compatible with the service. For more information, see the - * Azure Docs. - * - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param identifiers - * A list of {@link SignedIdentifier} objects that specify the permissions for the container. Please see - * here - * for more information. Passing null will clear all access policies. - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If {@link ContainerAccessConditions#modifiedAccessConditions()} has either - * {@link ModifiedAccessConditions#ifMatch()} or {@link ModifiedAccessConditions#ifNoneMatch()} set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.setAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setAccessPolicy(PublicAccessType accessType, - List identifiers, ContainerAccessConditions accessConditions) { - accessConditions = accessConditions == null ? new ContainerAccessConditions() : accessConditions; - - if (!validateNoEtag(accessConditions.modifiedAccessConditions())) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException("ETag access conditions are not supported for this API."); - } - - /* - We truncate to seconds because the service only supports nanoseconds or seconds, but doing an - OffsetDateTime.now will only give back milliseconds (more precise fields are zeroed and not serialized). This - allows for proper serialization with no real detriment to users as sub-second precision on active time for - signed identifiers is not really necessary. - */ - if (identifiers != null) { - for (SignedIdentifier identifier : identifiers) { - if (identifier.accessPolicy() != null && identifier.accessPolicy().start() != null) { - identifier.accessPolicy().start( - identifier.accessPolicy().start().truncatedTo(ChronoUnit.SECONDS)); - } - if (identifier.accessPolicy() != null && identifier.accessPolicy().expiry() != null) { - identifier.accessPolicy().expiry( - identifier.accessPolicy().expiry().truncatedTo(ChronoUnit.SECONDS)); - } - } - } - - return postProcessResponse(this.azureBlobStorage.containers() - .setAccessPolicyWithRestResponseAsync(null, identifiers, null, accessType, - null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), - Context.NONE)); - - } - - private boolean validateNoEtag(ModifiedAccessConditions modifiedAccessConditions) { - if (modifiedAccessConditions == null) { - return true; - } - return modifiedAccessConditions.ifMatch() == null && modifiedAccessConditions.ifNoneMatch() == null; - } - - /** - * Acquires a lease on the container for delete operations. The lease duration must be between 15 to - * 60 seconds, or infinite (-1). For more information, see the - * Azure Docs. - * - * @apiNote - * ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - * - * @param proposedId - * A {@code String} in any valid GUID format. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. - * A non-infinite lease can be between 15 and 60 seconds. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono acquireLease(String proposedId, int duration) { - return this.acquireLease(proposedId, duration, null); - } - - /** - * Acquires a lease on the container for delete operations. The lease duration must be between 15 to - * 60 seconds, or infinite (-1). For more information, see the - * Azure Docs. - * - * @param proposedID - * A {@code String} in any valid GUID format. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. - * A non-infinite lease can be between 15 and 60 seconds. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or - * {@link ModifiedAccessConditions#ifNoneMatch()} is set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono acquireLease(String proposedID, int duration, - ModifiedAccessConditions modifiedAccessConditions) { - if (!this.validateNoEtag(modifiedAccessConditions)) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException( - "ETag access conditions are not supported for this API."); - } - - return postProcessResponse(this.azureBlobStorage.containers().acquireLeaseWithRestResponseAsync( - null, null, duration, proposedID, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * Renews the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.renewLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono renewLease(String leaseID) { - return this.renewLease(leaseID, null); - } - - /** - * Renews the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or - * {@link ModifiedAccessConditions#ifNoneMatch()} is set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.renewLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono renewLease(String leaseID, - ModifiedAccessConditions modifiedAccessConditions) { - if (!this.validateNoEtag(modifiedAccessConditions)) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException( - "ETag access conditions are not supported for this API."); - } - - return postProcessResponse(this.azureBlobStorage.containers().renewLeaseWithRestResponseAsync(null, - leaseID, null, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * Releases the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.releaseLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono releaseLease(String leaseID) { - return this.releaseLease(leaseID, null); - } - - /** - * Releases the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or - * {@link ModifiedAccessConditions#ifNoneMatch()} is set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.releaseLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono releaseLease(String leaseID, - ModifiedAccessConditions modifiedAccessConditions) { - if (!this.validateNoEtag(modifiedAccessConditions)) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException( - "ETag access conditions are not supported for this API."); - } - - return postProcessResponse(this.azureBlobStorage.containers().releaseLeaseWithRestResponseAsync( - null, leaseID, null, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * Breaks the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @apiNote - * ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.breakLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - * - * @return Emits the successful response. - */ - public Mono breakLease() { - return this.breakLease(null, null); - } - - /** - * Breaks the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param breakPeriodInSeconds - * An optional {@code Integer} representing the proposed duration of seconds that the lease should continue - * before it is broken, between 0 and 60 seconds. This break period is only used if it is shorter than the time - * remaining on the lease. If longer, the time remaining on the lease is used. A new lease will not be - * available before the break period has expired, but the lease may be held for longer than the break period. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or - * {@link ModifiedAccessConditions#ifNoneMatch()} is set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.breakLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono breakLease(Integer breakPeriodInSeconds, - ModifiedAccessConditions modifiedAccessConditions) { - if (!this.validateNoEtag(modifiedAccessConditions)) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException( - "ETag access conditions are not supported for this API."); - } - - return postProcessResponse(this.azureBlobStorage.containers().breakLeaseWithRestResponseAsync(null, - null, breakPeriodInSeconds, null, modifiedAccessConditions, Context.NONE)); - - } - - /** - * Changes the container's leaseAccessConditions. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param proposedID - * A {@code String} in any valid GUID format. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.changeLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono changeLease(String leaseID, String proposedID) { - return this.changeLease(leaseID, proposedID, null); - } - - /** - * Changes the container's leaseAccessConditions. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param proposedID - * A {@code String} in any valid GUID format. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or - * {@link ModifiedAccessConditions#ifNoneMatch()} is set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.changeLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono changeLease(String leaseID, String proposedID, - ModifiedAccessConditions modifiedAccessConditions) { - if (!this.validateNoEtag(modifiedAccessConditions)) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException( - "ETag access conditions are not supported for this API."); - } - - return postProcessResponse(this.azureBlobStorage.containers().changeLeaseWithRestResponseAsync(null, - leaseID, proposedID, null, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * Returns a single segment of blobs starting from the specified Marker. Use an empty - * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. - * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned - * Marker) to get the next segment. For more information, see the - * Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListBlobsFlatSegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param options - * {@link ListBlobsOptions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat "Sample code for ContainerAsyncClient.listBlobsFlatSegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat_helper "helper code for ContainerAsyncClient.listBlobsFlatSegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono listBlobsFlatSegment(String marker, ListBlobsOptions options) { - options = options == null ? new ListBlobsOptions() : options; - - return postProcessResponse(this.azureBlobStorage.containers() - .listBlobFlatSegmentWithRestResponseAsync(null, options.prefix(), marker, - options.maxResults(), options.details().toList(), null, null, Context.NONE)); - } - - /** - * Returns a single segment of blobs and blob prefixes starting from the specified Marker. Use an empty - * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. - * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned - * Marker) to get the next segment. For more information, see the - * Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListBlobsHierarchySegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param delimiter - * The operation returns a BlobPrefix element in the response body that acts as a placeholder for all blobs - * whose names begin with the same substring up to the appearance of the delimiter character. The delimiter may - * be a single character or a string. - * @param options - * {@link ListBlobsOptions} - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If {@link ListBlobsOptions#details()} has {@link BlobListDetails#snapshots()} - * set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy "Sample code for ContainerAsyncClient.listBlobsHierarchySegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy_helper "helper code for ContainerAsyncClient.listBlobsHierarchySegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono listBlobsHierarchySegment(String marker, String delimiter, - ListBlobsOptions options) { - options = options == null ? new ListBlobsOptions() : options; - if (options.details().snapshots()) { - throw new UnsupportedOperationException("Including snapshots in a hierarchical listing is not supported."); - } - - return postProcessResponse(this.azureBlobStorage.containers() - .listBlobHierarchySegmentWithRestResponseAsync(null, delimiter, options.prefix(), marker, - options.maxResults(), options.details().toList(), null, null, Context.NONE)); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for ContainerAsyncClient.getAccountInfo")] \n - * For more samples, please see the [Samples file] (https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getAccountInfo() { - return postProcessResponse( - this.azureBlobStorage.containers().getAccountInfoWithRestResponseAsync(null, Context.NONE)); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClient.java index b8c1f9d8bd09e..40ed0d25daa1f 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClient.java @@ -37,14 +37,13 @@ * for more information on containers. */ public final class ContainerClient { - private ContainerAsyncClient containerAsyncClient; - public static final String ROOT_CONTAINER_NAME = "$root"; + public static final String ROOT_CONTAINER_NAME = ContainerAsyncClient.ROOT_CONTAINER_NAME; - public static final String STATIC_WEBSITE_CONTAINER_NAME = "$web"; + public static final String STATIC_WEBSITE_CONTAINER_NAME = ContainerAsyncClient.STATIC_WEBSITE_CONTAINER_NAME; - public static final String LOG_CONTAINER_NAME = "$logs"; + public static final String LOG_CONTAINER_NAME = ContainerAsyncClient.LOG_CONTAINER_NAME; /** * Package-private constructor for use by {@link ContainerClientBuilder}. diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerRawClient.java deleted file mode 100644 index 19d10f5444ee5..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerRawClient.java +++ /dev/null @@ -1,652 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.http.rest.Response; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.ContainerAccessConditions; -import com.azure.storage.blob.models.ContainerAccessPolicies; -import com.azure.storage.blob.models.ContainersAcquireLeaseResponse; -import com.azure.storage.blob.models.ContainersBreakLeaseResponse; -import com.azure.storage.blob.models.ContainersChangeLeaseResponse; -import com.azure.storage.blob.models.ContainersCreateResponse; -import com.azure.storage.blob.models.ContainersDeleteResponse; -import com.azure.storage.blob.models.ContainersGetAccountInfoResponse; -import com.azure.storage.blob.models.ContainersGetPropertiesResponse; -import com.azure.storage.blob.models.ContainersListBlobFlatSegmentResponse; -import com.azure.storage.blob.models.ContainersListBlobHierarchySegmentResponse; -import com.azure.storage.blob.models.ContainersReleaseLeaseResponse; -import com.azure.storage.blob.models.ContainersRenewLeaseResponse; -import com.azure.storage.blob.models.ContainersSetAccessPolicyResponse; -import com.azure.storage.blob.models.ContainersSetMetadataResponse; -import com.azure.storage.blob.models.LeaseAccessConditions; -import com.azure.storage.blob.models.ListBlobsOptions; -import com.azure.storage.blob.models.Metadata; -import com.azure.storage.blob.models.ModifiedAccessConditions; -import com.azure.storage.blob.models.PublicAccessType; -import com.azure.storage.blob.models.SignedIdentifier; -import reactor.core.publisher.Mono; - -import java.time.Duration; -import java.util.List; - -/** - * Represents a URL to a container. It may be obtained by direct construction or via the create method on a - * {@link StorageAsyncRawClient} object. This class does not hold any state about a particular blob but is instead a convenient way - * of sending off appropriate requests to the resource on the service. It may also be used to construct URLs to blobs. - * Please refer to the - * Azure Docs - * for more information on containers. - */ -final class ContainerRawClient { - - private ContainerAsyncRawClient containerAsyncRawClient; - - public static final String ROOT_CONTAINER_NAME = "$root"; - - public static final String STATIC_WEBSITE_CONTAINER_NAME = "$web"; - - public static final String LOG_CONTAINER_NAME = "$logs"; - - - /** - * Creates a {@code ContainerAsyncClient} object pointing to the account specified by the URL and using the provided - * pipeline to make HTTP requests. - */ - ContainerRawClient(AzureBlobStorageImpl azureBlobStorage) { - this.containerAsyncRawClient = new ContainerAsyncRawClient(azureBlobStorage); - } - - /** - * Creates a new container within a storage account. If a container with the same name already exists, the operation - * fails. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersCreateResponse create() { - return this.create(null, null, null); - } - - /** - * Creates a new container within a storage account. If a container with the same name already exists, the operation - * fails. For more information, see the - * Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersCreateResponse create(Metadata metadata, PublicAccessType accessType, Duration timeout) { - Mono response = containerAsyncRawClient.create(metadata, accessType); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Marks the specified container for deletion. The container and any blobs contained within it are later - * deleted during garbage collection. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.delete")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersDeleteResponse delete() { - return this.delete(null, null); - } - - /** - * Marks the specified container for deletion. The container and any blobs contained within it are later - * deleted during garbage collection. For more information, see the - * Azure Docs. - * - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.delete")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersDeleteResponse delete(ContainerAccessConditions accessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.delete(accessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Returns the container's metadata and system properties. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersGetPropertiesResponse getProperties() { - return this.getProperties(null, null); - } - - /** - * Returns the container's metadata and system properties. For more information, see the - * Azure Docs. - * - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersGetPropertiesResponse getProperties(LeaseAccessConditions leaseAccessConditions, - Duration timeout) { - Mono response = containerAsyncRawClient.getProperties(leaseAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Sets the container's metadata. For more information, see the - * Azure Docs. - * - * @param metadata - * {@link Metadata} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.setMetadata")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersSetMetadataResponse setMetadata(Metadata metadata) { - return this.setMetadata(metadata, null, null); - } - - /** - * Sets the container's metadata. For more information, see the - * Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.setMetadata")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersSetMetadataResponse setMetadata(Metadata metadata, - ContainerAccessConditions accessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.setMetadata(metadata, accessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Returns the container's permissions. The permissions indicate whether container's blobs may be accessed publicly. - * For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.getAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Response getAccessPolicy() { - return this.getAccessPolicy(null, null); - } - - /** - * Returns the container's permissions. The permissions indicate whether container's blobs may be accessed publicly. - * For more information, see the - * Azure Docs. - * - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.getAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Response getAccessPolicy(LeaseAccessConditions leaseAccessConditions, - Duration timeout) { - Mono> response = containerAsyncRawClient.getAccessPolicy(leaseAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Sets the container's permissions. The permissions indicate whether blobs in a container may be accessed publicly. - * Note that, for each signed identifier, we will truncate the start and expiry times to the nearest second to - * ensure the time formatting is compatible with the service. For more information, see the - * Azure Docs. - * - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param identifiers - * A list of {@link SignedIdentifier} objects that specify the permissions for the container. Please see - * here - * for more information. Passing null will clear all access policies. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.setAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersSetAccessPolicyResponse setAccessPolicy(PublicAccessType accessType, - List identifiers) { - return this.setAccessPolicy(accessType, identifiers, null, null); - } - - /** - * Sets the container's permissions. The permissions indicate whether blobs in a container may be accessed publicly. - * Note that, for each signed identifier, we will truncate the start and expiry times to the nearest second to - * ensure the time formatting is compatible with the service. For more information, see the - * Azure Docs. - * - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param identifiers - * A list of {@link SignedIdentifier} objects that specify the permissions for the container. Please see - * here - * for more information. Passing null will clear all access policies. - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.setAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersSetAccessPolicyResponse setAccessPolicy(PublicAccessType accessType, - List identifiers, ContainerAccessConditions accessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.setAccessPolicy(accessType, identifiers, accessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Acquires a lease on the container for delete operations. The lease duration must be between 15 to - * 60 seconds, or infinite (-1). For more information, see the - * Azure Docs. - * - * @apiNote - * ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - * - * @param proposedId - * A {@code String} in any valid GUID format. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. - * A non-infinite lease can be between 15 and 60 seconds. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersAcquireLeaseResponse acquireLease(String proposedId, int duration) { - return this.acquireLease(proposedId, duration, null, null); - } - - /** - * Acquires a lease on the container for delete operations. The lease duration must be between 15 to - * 60 seconds, or infinite (-1). For more information, see the - * Azure Docs. - * - * @param proposedID - * A {@code String} in any valid GUID format. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. - * A non-infinite lease can be between 15 and 60 seconds. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersAcquireLeaseResponse acquireLease(String proposedID, int duration, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.acquireLease(proposedID, duration, modifiedAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Renews the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.renewLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersRenewLeaseResponse renewLease(String leaseID) { - return this.renewLease(leaseID, null, null); - } - - /** - * Renews the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.renewLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersRenewLeaseResponse renewLease(String leaseID, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.renewLease(leaseID, modifiedAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Releases the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.releaseLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersReleaseLeaseResponse releaseLease(String leaseID) { - return this.releaseLease(leaseID, null, null); - } - - /** - * Releases the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.releaseLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersReleaseLeaseResponse releaseLease(String leaseID, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.releaseLease(leaseID, modifiedAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Breaks the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @apiNote - * ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.breakLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - * - * @return Emits the successful response. - */ - public ContainersBreakLeaseResponse breakLease() { - return this.breakLease(null, null, null); - } - - /** - * Breaks the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param breakPeriodInSeconds - * An optional {@code Integer} representing the proposed duration of seconds that the lease should continue - * before it is broken, between 0 and 60 seconds. This break period is only used if it is shorter than the time - * remaining on the lease. If longer, the time remaining on the lease is used. A new lease will not be - * available before the break period has expired, but the lease may be held for longer than the break period. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.breakLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersBreakLeaseResponse breakLease(Integer breakPeriodInSeconds, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.breakLease(breakPeriodInSeconds, modifiedAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Changes the container's leaseAccessConditions. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param proposedID - * A {@code String} in any valid GUID format. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.changeLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersChangeLeaseResponse changeLease(String leaseID, String proposedID) { - return this.changeLease(leaseID, proposedID, null, null); - } - - /** - * Changes the container's leaseAccessConditions. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param proposedID - * A {@code String} in any valid GUID format. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.changeLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersChangeLeaseResponse changeLease(String leaseID, String proposedID, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.changeLease(leaseID, proposedID, modifiedAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Returns a single segment of blobs starting from the specified Marker. Use an empty - * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. - * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned - * Marker) to get the next segment. For more information, see the - * Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListBlobsFlatSegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param options - * {@link ListBlobsOptions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat "Sample code for ContainerAsyncClient.listBlobsFlatSegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat_helper "helper code for ContainerAsyncClient.listBlobsFlatSegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersListBlobFlatSegmentResponse listBlobsFlatSegment(String marker, ListBlobsOptions options) { - return this.listBlobsFlatSegment(marker, options, null); - } - - /** - * Returns a single segment of blobs starting from the specified Marker. Use an empty - * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. - * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned - * Marker) to get the next segment. For more information, see the - * Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListBlobsFlatSegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param options - * {@link ListBlobsOptions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat "Sample code for ContainerAsyncClient.listBlobsFlatSegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat_helper "helper code for ContainerAsyncClient.listBlobsFlatSegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersListBlobFlatSegmentResponse listBlobsFlatSegment(String marker, ListBlobsOptions options, - Duration timeout) { - Mono response = containerAsyncRawClient.listBlobsFlatSegment(marker, options); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Returns a single segment of blobs and blob prefixes starting from the specified Marker. Use an empty - * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. - * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned - * Marker) to get the next segment. For more information, see the - * Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListBlobsHierarchySegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param delimiter - * The operation returns a BlobPrefix element in the response body that acts as a placeholder for all blobs - * whose names begin with the same substring up to the appearance of the delimiter character. The delimiter may - * be a single character or a string. - * @param options - * {@link ListBlobsOptions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy "Sample code for ContainerAsyncClient.listBlobsHierarchySegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy_helper "helper code for ContainerAsyncClient.listBlobsHierarchySegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersListBlobHierarchySegmentResponse listBlobsHierarchySegment(String marker, String delimiter, - ListBlobsOptions options) { - return this.listBlobsHierarchySegment(marker, delimiter, options, null); - } - - /** - * Returns a single segment of blobs and blob prefixes starting from the specified Marker. Use an empty - * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. - * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned - * Marker) to get the next segment. For more information, see the - * Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListBlobsHierarchySegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param delimiter - * The operation returns a BlobPrefix element in the response body that acts as a placeholder for all blobs - * whose names begin with the same substring up to the appearance of the delimiter character. The delimiter may - * be a single character or a string. - * @param options - * {@link ListBlobsOptions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy "Sample code for ContainerAsyncClient.listBlobsHierarchySegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy_helper "helper code for ContainerAsyncClient.listBlobsHierarchySegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersListBlobHierarchySegmentResponse listBlobsHierarchySegment(String marker, String delimiter, - ListBlobsOptions options, Duration timeout) { - Mono response = containerAsyncRawClient.listBlobsHierarchySegment(marker, delimiter, options); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for ContainerAsyncClient.getAccountInfo")] \n - * For more samples, please see the [Samples file] (https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersGetAccountInfoResponse getAccountInfo() { - return this.getAccountInfo(null); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for ContainerAsyncClient.getAccountInfo")] \n - * For more samples, please see the [Samples file] (https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersGetAccountInfoResponse getAccountInfo(Duration timeout) { - Mono response = containerAsyncRawClient.getAccountInfo(); - return Utility.blockWithOptionalTimeout(response, timeout); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncClient.java index 073df29744582..72f052fd27573 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncClient.java @@ -5,6 +5,8 @@ import com.azure.core.http.rest.Response; import com.azure.core.http.rest.SimpleResponse; +import com.azure.core.implementation.http.UrlBuilder; +import com.azure.core.util.Context; import com.azure.storage.blob.implementation.AzureBlobStorageBuilder; import com.azure.storage.blob.models.BlobAccessConditions; import com.azure.storage.blob.models.BlobHTTPHeaders; @@ -17,12 +19,14 @@ import com.azure.storage.blob.models.PageRange; import com.azure.storage.blob.models.SequenceNumberActionType; import com.azure.storage.blob.models.SourceModifiedAccessConditions; -import io.netty.buffer.Unpooled; +import io.netty.buffer.ByteBuf; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.net.MalformedURLException; import java.net.URL; -import java.nio.ByteBuffer; + +import static com.azure.storage.blob.Utility.postProcessResponse; /** * Client to a page blob. It may only be instantiated through a {@link PageBlobClientBuilder}, via @@ -48,9 +52,6 @@ * object through {@link Mono#toFuture()}. */ public final class PageBlobAsyncClient extends BlobAsyncClient { - - final PageBlobAsyncRawClient pageBlobAsyncRawClient; - /** * Indicates the number of bytes in a page. */ @@ -67,7 +68,6 @@ public final class PageBlobAsyncClient extends BlobAsyncClient { */ PageBlobAsyncClient(AzureBlobStorageBuilder azureBlobStorageBuilder, String snapshot) { super(azureBlobStorageBuilder, snapshot); - this.pageBlobAsyncRawClient = new PageBlobAsyncRawClient(azureBlobStorageBuilder.build(), snapshot); } /** @@ -104,13 +104,30 @@ public Mono> create(long size) { * @param accessConditions * {@link BlobAccessConditions} * - * @return - * A reactive response containing the information of the created page blob. + * @return A reactive response containing the information of the created page blob. + * @throws IllegalArgumentException If {@code size} isn't a multiple of {@link PageBlobAsyncClient#PAGE_BYTES} + * or {@code sequenceNumber} isn't null and is less than 0. */ public Mono> create(long size, Long sequenceNumber, BlobHTTPHeaders headers, Metadata metadata, BlobAccessConditions accessConditions) { - return pageBlobAsyncRawClient - .create(size, sequenceNumber, headers, metadata, accessConditions) + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + if (size % PAGE_BYTES != 0) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("size must be a multiple of PageBlobAsyncClient.PAGE_BYTES."); + } + if (sequenceNumber != null && sequenceNumber < 0) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("SequenceNumber must be greater than or equal to 0."); + } + metadata = metadata == null ? new Metadata() : metadata; + + return postProcessResponse(this.azureBlobStorage.pageBlobs().createWithRestResponseAsync(null, + null, 0, size, null, metadata, null, null, + null, sequenceNumber, null, headers, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new PageBlobItem(rb.deserializedHeaders()))); } @@ -133,7 +150,7 @@ public Mono> create(long size, Long sequenceNumber, BlobH * @return * A reactive response containing the information of the uploaded pages. */ - public Mono> uploadPages(PageRange pageRange, Flux body) { + public Mono> uploadPages(PageRange pageRange, Flux body) { return this.uploadPages(pageRange, body, null); } @@ -155,13 +172,25 @@ public Mono> uploadPages(PageRange pageRange, Flux> uploadPages(PageRange pageRange, Flux body, + public Mono> uploadPages(PageRange pageRange, Flux body, PageBlobAccessConditions pageBlobAccessConditions) { - return pageBlobAsyncRawClient - .uploadPages(pageRange, body.map(Unpooled::wrappedBuffer), pageBlobAccessConditions) + pageBlobAccessConditions = pageBlobAccessConditions == null ? new PageBlobAccessConditions() : pageBlobAccessConditions; + + if (pageRange == null) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("pageRange cannot be null."); + } + String pageRangeStr = pageRangeToString(pageRange); + + return postProcessResponse(this.azureBlobStorage.pageBlobs().uploadPagesWithRestResponseAsync(null, + null, body, pageRange.end() - pageRange.start() + 1, null, + null, pageRangeStr, null, null, null, null, + pageBlobAccessConditions.leaseAccessConditions(), pageBlobAccessConditions.sequenceNumberAccessConditions(), + pageBlobAccessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new PageBlobItem(rb.deserializedHeaders()))); } @@ -218,15 +247,33 @@ public Mono> uploadPagesFromURL(PageRange range, URL sour * @param sourceAccessConditions * {@link SourceModifiedAccessConditions} * - * @return - * A reactive response containing the information of the uploaded pages. + * @return A reactive response containing the information of the uploaded pages. + * @throws IllegalArgumentException If {@code range} is {@code null} */ public Mono> uploadPagesFromURL(PageRange range, URL sourceURL, Long sourceOffset, byte[] sourceContentMD5, PageBlobAccessConditions destAccessConditions, SourceModifiedAccessConditions sourceAccessConditions) { + if (range == null) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("range cannot be null."); + } + + String rangeString = pageRangeToString(range); - return pageBlobAsyncRawClient - .uploadPagesFromURL(range, sourceURL, sourceOffset, sourceContentMD5, destAccessConditions, sourceAccessConditions) + if (sourceOffset == null) { + sourceOffset = 0L; + } + + String sourceRangeString = pageRangeToString(new PageRange().start(sourceOffset).end(sourceOffset + (range.end() - range.start()))); + + destAccessConditions = destAccessConditions == null ? new PageBlobAccessConditions() : destAccessConditions; + + return postProcessResponse(this.azureBlobStorage.pageBlobs().uploadPagesFromURLWithRestResponseAsync( + null, null, sourceURL, sourceRangeString, 0, rangeString, sourceContentMD5, + null, null, destAccessConditions.leaseAccessConditions(), + destAccessConditions.sequenceNumberAccessConditions(), destAccessConditions.modifiedAccessConditions(), + sourceAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new PageBlobItem(rb.deserializedHeaders()))); } @@ -259,13 +306,23 @@ public Mono> clearPages(PageRange pageRange) { * @param pageBlobAccessConditions * {@link PageBlobAccessConditions} * - * @return - * A reactive response containing the information of the cleared pages. + * @return A reactive response containing the information of the cleared pages. + * @throws IllegalArgumentException If {@code pageRange} is {@code null} */ public Mono> clearPages(PageRange pageRange, PageBlobAccessConditions pageBlobAccessConditions) { - return pageBlobAsyncRawClient - .clearPages(pageRange, pageBlobAccessConditions) + pageBlobAccessConditions = pageBlobAccessConditions == null ? new PageBlobAccessConditions() : pageBlobAccessConditions; + if (pageRange == null) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("pageRange cannot be null."); + } + String pageRangeStr = pageRangeToString(pageRange); + + return postProcessResponse(this.azureBlobStorage.pageBlobs().clearPagesWithRestResponseAsync(null, + null, 0, null, pageRangeStr, null, + pageBlobAccessConditions.leaseAccessConditions(), pageBlobAccessConditions.sequenceNumberAccessConditions(), + pageBlobAccessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new PageBlobItem(rb.deserializedHeaders()))); } @@ -295,10 +352,14 @@ public Flux getPageRanges(BlobRange blobRange) { * @return * A reactive response emitting all the page ranges. */ - public Flux getPageRanges(BlobRange blobRange, - BlobAccessConditions accessConditions) { - return pageBlobAsyncRawClient - .getPageRanges(blobRange, accessConditions) + public Flux getPageRanges(BlobRange blobRange, BlobAccessConditions accessConditions) { + blobRange = blobRange == null ? new BlobRange(0) : blobRange; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.pageBlobs().getPageRangesWithRestResponseAsync( + null, null, snapshot, null, null, blobRange.toHeaderValue(), + null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), + Context.NONE)) .flatMapMany(response -> Flux.fromIterable(response.value().pageRange())); } @@ -333,13 +394,21 @@ public Flux getPageRangesDiff(BlobRange blobRange, String prevSnapsho * @param accessConditions * {@link BlobAccessConditions} * - * @return - * A reactive response emitting all the different page ranges. + * @return A reactive response emitting all the different page ranges. + * @throws IllegalArgumentException If {@code prevSnapshot} is {@code null} */ - public Flux getPageRangesDiff(BlobRange blobRange, String prevSnapshot, - BlobAccessConditions accessConditions) { - return pageBlobAsyncRawClient - .getPageRangesDiff(blobRange, prevSnapshot, accessConditions) + public Flux getPageRangesDiff(BlobRange blobRange, String prevSnapshot, BlobAccessConditions accessConditions) { + blobRange = blobRange == null ? new BlobRange(0) : blobRange; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + if (prevSnapshot == null) { + throw new IllegalArgumentException("prevSnapshot cannot be null"); + } + + return postProcessResponse(this.azureBlobStorage.pageBlobs().getPageRangesDiffWithRestResponseAsync( + null, null, snapshot, null, null, prevSnapshot, + blobRange.toHeaderValue(), null, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .flatMapMany(response -> Flux.fromIterable(response.value().pageRange())); } @@ -368,12 +437,20 @@ public Mono> resize(long size) { * @param accessConditions * {@link BlobAccessConditions} * - * @return - * A reactive response emitting the resized page blob. + * @return A reactive response emitting the resized page blob. + * @throws IllegalArgumentException If {@code size} isn't a multiple of {@link PageBlobAsyncClient#PAGE_BYTES} */ public Mono> resize(long size, BlobAccessConditions accessConditions) { - return pageBlobAsyncRawClient - .resize(size, accessConditions) + if (size % PAGE_BYTES != 0) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("size must be a multiple of PageBlobAsyncClient.PAGE_BYTES."); + } + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.pageBlobs().resizeWithRestResponseAsync(null, + null, size, null, null, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new PageBlobItem(rb.deserializedHeaders()))); } @@ -407,13 +484,22 @@ public Mono> updateSequenceNumber(SequenceNumberActionTyp * @param accessConditions * {@link BlobAccessConditions} * - * @return - * A reactive response emitting the updated page blob. + * @return A reactive response emitting the updated page blob. + * @throws IllegalArgumentException If {@code sequenceNumber} isn't null and is less than 0 */ - public Mono> updateSequenceNumber(SequenceNumberActionType action, - Long sequenceNumber, BlobAccessConditions accessConditions) { - return pageBlobAsyncRawClient - .updateSequenceNumber(action, sequenceNumber, accessConditions) + public Mono> updateSequenceNumber(SequenceNumberActionType action, Long sequenceNumber, BlobAccessConditions accessConditions) { + if (sequenceNumber != null && sequenceNumber < 0) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("SequenceNumber must be greater than or equal to 0."); + } + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + sequenceNumber = action == SequenceNumberActionType.INCREMENT ? null : sequenceNumber; + + return postProcessResponse( + this.azureBlobStorage.pageBlobs().updateSequenceNumberWithRestResponseAsync(null, + null, action, null, sequenceNumber, null, + accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new PageBlobItem(rb.deserializedHeaders()))); } @@ -454,13 +540,37 @@ public Mono> copyIncremental(URL source, String snapsho * to construct conditions related to when the blob was changed relative to the given request. The request * will fail if the specified condition is not satisfied. * - * @return - * A reactive response emitting the copy status. + * @return A reactive response emitting the copy status. + * @throws Error If {@code source} and {@code snapshot} form a malformed URL. */ - public Mono> copyIncremental(URL source, String snapshot, - ModifiedAccessConditions modifiedAccessConditions) { - return pageBlobAsyncRawClient - .copyIncremental(source, snapshot, modifiedAccessConditions) + public Mono> copyIncremental(URL source, String snapshot, ModifiedAccessConditions modifiedAccessConditions) { + UrlBuilder builder = UrlBuilder.parse(source); + builder.setQueryParameter(Constants.SNAPSHOT_QUERY_PARAMETER, snapshot); + try { + source = builder.toURL(); + } catch (MalformedURLException e) { + // We are parsing a valid url and adding a query parameter. If this fails, we can't recover. + throw new Error(e); + } + return postProcessResponse(this.azureBlobStorage.pageBlobs().copyIncrementalWithRestResponseAsync( + null, null, source, null, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().copyStatus())); } + + private static String pageRangeToString(PageRange pageRange) { + if (pageRange.start() < 0 || pageRange.end() <= 0) { + throw new IllegalArgumentException("PageRange's start and end values must be greater than or equal to " + + "0 if specified."); + } + if (pageRange.start() % PAGE_BYTES != 0) { + throw new IllegalArgumentException("PageRange's start value must be a multiple of 512."); + } + if (pageRange.end() % PAGE_BYTES != PAGE_BYTES - 1) { + throw new IllegalArgumentException("PageRange's end value must be 1 less than a multiple of 512."); + } + if (pageRange.end() <= pageRange.start()) { + throw new IllegalArgumentException("PageRange's End value must be after the start."); + } + return "bytes=" + pageRange.start() + '-' + pageRange.end(); + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncRawClient.java deleted file mode 100644 index fba55bd8d7455..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncRawClient.java +++ /dev/null @@ -1,620 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.implementation.http.UrlBuilder; -import com.azure.core.util.Context; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.BlobAccessConditions; -import com.azure.storage.blob.models.BlobHTTPHeaders; -import com.azure.storage.blob.models.BlobRange; -import com.azure.storage.blob.models.Metadata; -import com.azure.storage.blob.models.ModifiedAccessConditions; -import com.azure.storage.blob.models.PageBlobAccessConditions; -import com.azure.storage.blob.models.PageBlobsClearPagesResponse; -import com.azure.storage.blob.models.PageBlobsCopyIncrementalResponse; -import com.azure.storage.blob.models.PageBlobsCreateResponse; -import com.azure.storage.blob.models.PageBlobsGetPageRangesDiffResponse; -import com.azure.storage.blob.models.PageBlobsGetPageRangesResponse; -import com.azure.storage.blob.models.PageBlobsResizeResponse; -import com.azure.storage.blob.models.PageBlobsUpdateSequenceNumberResponse; -import com.azure.storage.blob.models.PageBlobsUploadPagesFromURLResponse; -import com.azure.storage.blob.models.PageBlobsUploadPagesResponse; -import com.azure.storage.blob.models.PageRange; -import com.azure.storage.blob.models.SequenceNumberActionType; -import com.azure.storage.blob.models.SourceModifiedAccessConditions; -import io.netty.buffer.ByteBuf; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.net.MalformedURLException; -import java.net.URL; - -import static com.azure.storage.blob.Utility.postProcessResponse; - -/** - * Represents a URL to a page blob. It may be obtained by direct construction or via the create method on a - * {@link ContainerAsyncClient} object. This class does not hold any state about a particular blob but is instead a convenient - * way of sending off appropriate requests to the resource on the service. Please refer to the - * Azure Docs - * for more information. - */ -final class PageBlobAsyncRawClient extends BlobAsyncRawClient { - - /** - * Indicates the number of bytes in a page. - */ - public static final int PAGE_BYTES = 512; - - /** - * Indicates the maximum number of bytes that may be sent in a call to putPage. - */ - public static final int MAX_PUT_PAGES_BYTES = 4 * Constants.MB; - - /** - * Creates a {@code PageBlobAsyncRawClient} object pointing to the account specified by the URL and using the provided - * pipeline to make HTTP requests. - * - */ - PageBlobAsyncRawClient(AzureBlobStorageImpl azureBlobStorage, String snapshot) { - super(azureBlobStorage, snapshot); - } - - private static String pageRangeToString(PageRange pageRange) { - if (pageRange.start() < 0 || pageRange.end() <= 0) { - throw new IllegalArgumentException("PageRange's start and end values must be greater than or equal to " - + "0 if specified."); - } - if (pageRange.start() % PageBlobAsyncRawClient.PAGE_BYTES != 0) { - throw new IllegalArgumentException("PageRange's start value must be a multiple of 512."); - } - if (pageRange.end() % PageBlobAsyncRawClient.PAGE_BYTES != PageBlobAsyncRawClient.PAGE_BYTES - 1) { - throw new IllegalArgumentException("PageRange's end value must be 1 less than a multiple of 512."); - } - if (pageRange.end() <= pageRange.start()) { - throw new IllegalArgumentException("PageRange's End value must be after the start."); - } - return new StringBuilder("bytes=").append(pageRange.start()).append('-').append(pageRange.end()).toString(); - } - - /** - * Creates a page blob of the specified length. Call PutPage to upload data data to a page blob. - * For more information, see the - * Azure Docs. - * - * @param size - * Specifies the maximum size for the page blob, up to 8 TB. The page blob size must be aligned to a - * 512-byte boundary. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono create(long size) { - return this.create(size, null, null, null, null); - } - - /** - * Creates a page blob of the specified length. Call PutPage to upload data data to a page blob. - * For more information, see the - * Azure Docs. - * - * @param size - * Specifies the maximum size for the page blob, up to 8 TB. The page blob size must be aligned to a - * 512-byte boundary. - * @param sequenceNumber - * A user-controlled value that you can use to track requests. The value of the sequence number must be - * between 0 and 2^63 - 1.The default value is 0. - * @param headers - * {@link BlobHTTPHeaders} - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code size} isn't a multiple of {@link PageBlobAsyncRawClient#PAGE_BYTES} - * or {@code sequenceNumber} isn't null and is less than 0. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono create(long size, Long sequenceNumber, BlobHTTPHeaders headers, - Metadata metadata, BlobAccessConditions accessConditions) { - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - if (size % PageBlobAsyncRawClient.PAGE_BYTES != 0) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("size must be a multiple of PageBlobAsyncRawClient.PAGE_BYTES."); - } - if (sequenceNumber != null && sequenceNumber < 0) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("SequenceNumber must be greater than or equal to 0."); - } - metadata = metadata == null ? new Metadata() : metadata; - - return postProcessResponse(this.azureBlobStorage.pageBlobs().createWithRestResponseAsync(null, - null, 0, size, null, metadata, null, null, - null, sequenceNumber, null, headers, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Writes 1 or more pages to the page blob. The start and end offsets must be a multiple of 512. - * For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - * - * @param pageRange - * A {@link PageRange} object. Given that pages must be aligned with 512-byte boundaries, the start offset must - * be a modulus of 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges are - * 0-511, 512-1023, etc. - * @param body - * The data to upload. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.uploadPages")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono uploadPages(PageRange pageRange, Flux body) { - return this.uploadPages(pageRange, body, null); - } - - /** - * Writes 1 or more pages to the page blob. The start and end offsets must be a multiple of 512. - * For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - * - * @param pageRange - * A {@link PageRange} object. Given that pages must be aligned with 512-byte boundaries, the start offset - * must be a modulus of 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges - * are 0-511, 512-1023, etc. - * @param body - * The data to upload. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param pageBlobAccessConditions - * {@link PageBlobAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code pageRange} is {@code null} - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.uploadPages")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono uploadPages(PageRange pageRange, Flux body, - PageBlobAccessConditions pageBlobAccessConditions) { - pageBlobAccessConditions = pageBlobAccessConditions == null ? new PageBlobAccessConditions() - : pageBlobAccessConditions; - - if (pageRange == null) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("pageRange cannot be null."); - } - String pageRangeStr = pageRangeToString(pageRange); - - return postProcessResponse(this.azureBlobStorage.pageBlobs().uploadPagesWithRestResponseAsync(null, - null, body, pageRange.end() - pageRange.start() + 1, null, - null, pageRangeStr, null, null, null, null, - pageBlobAccessConditions.leaseAccessConditions(), pageBlobAccessConditions.sequenceNumberAccessConditions(), - pageBlobAccessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Writes 1 or more pages from the source page blob to this page blob. The start and end offsets must be a multiple - * of 512. - * For more information, see the - * Azure Docs. - *

- * - * @param range - * A {@link PageRange} object. Given that pages must be aligned with 512-byte boundaries, the start offset - * must be a modulus of 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges - * are 0-511, 512-1023, etc. - * @param sourceURL - * The url to the blob that will be the source of the copy. A source blob in the same storage account can be - * authenticated via Shared Key. However, if the source is a blob in another account, the source blob must - * either be public or must be authenticated via a shared access signature. If the source blob is public, no - * authentication is required to perform the operation. - * @param sourceOffset - * The source offset to copy from. Pass null or 0 to copy from the beginning of source page blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_from_url "Sample code for PageBlobAsyncRawClient.uploadPagesFromURL")] - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono uploadPagesFromURL(PageRange range, URL sourceURL, Long sourceOffset) { - return this.uploadPagesFromURL(range, sourceURL, sourceOffset, null, null, - null); - } - - /** - * Writes 1 or more pages from the source page blob to this page blob. The start and end offsets must be a multiple - * of 512. - * For more information, see the - * Azure Docs. - *

- * - * @param range - * The destination {@link PageRange} range. Given that pages must be aligned with 512-byte boundaries, the start offset - * must be a modulus of 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges - * are 0-511, 512-1023, etc. - * @param sourceURL - * The url to the blob that will be the source of the copy. A source blob in the same storage account can be - * authenticated via Shared Key. However, if the source is a blob in another account, the source blob must - * either be public or must be authenticated via a shared access signature. If the source blob is public, no - * authentication is required to perform the operation. - * @param sourceOffset - * The source offset to copy from. Pass null or 0 to copy from the beginning of source blob. - * @param sourceContentMD5 - * An MD5 hash of the block content from the source blob. If specified, the service will calculate the MD5 - * of the received data and fail the request if it does not match the provided MD5. - * @param destAccessConditions - * {@link PageBlobAccessConditions} - * @param sourceAccessConditions - * {@link SourceModifiedAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code range} is {@code null} - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_from_url "Sample code for PageBlobAsyncRawClient.uploadPagesFromURL")] - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono uploadPagesFromURL(PageRange range, URL sourceURL, Long sourceOffset, - byte[] sourceContentMD5, PageBlobAccessConditions destAccessConditions, - SourceModifiedAccessConditions sourceAccessConditions) { - - if (range == null) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("range cannot be null."); - } - - String rangeString = pageRangeToString(range); - - if (sourceOffset == null) { - sourceOffset = 0L; - } - - String sourceRangeString = pageRangeToString(new PageRange().start(sourceOffset).end(sourceOffset + (range.end() - range.start()))); - - destAccessConditions = destAccessConditions == null ? new PageBlobAccessConditions() : destAccessConditions; - - return postProcessResponse(this.azureBlobStorage.pageBlobs().uploadPagesFromURLWithRestResponseAsync( - null, null, sourceURL, sourceRangeString, 0, rangeString, sourceContentMD5, - null, null, destAccessConditions.leaseAccessConditions(), - destAccessConditions.sequenceNumberAccessConditions(), destAccessConditions.modifiedAccessConditions(), - sourceAccessConditions, Context.NONE)); - } - - /** - * Frees the specified pages from the page blob. - * For more information, see the - * Azure Docs. - * - * @param pageRange - * A {@link PageRange} object. Given that pages must be aligned with 512-byte boundaries, the start offset - * must be a modulus of 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges - * are 0-511, 512-1023, etc. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.clearPages")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono clearPages(PageRange pageRange) { - return this.clearPages(pageRange, null); - } - - /** - * Frees the specified pages from the page blob. - * For more information, see the - * Azure Docs. - * - * @param pageRange - * A {@link PageRange} object. Given that pages must be aligned with 512-byte boundaries, the start offset - * must be a modulus of 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges - * are 0-511, 512-1023, etc. - * @param pageBlobAccessConditions - * {@link PageBlobAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code pageRange} is {@code null} - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.clearPages")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono clearPages(PageRange pageRange, - PageBlobAccessConditions pageBlobAccessConditions) { - pageBlobAccessConditions = pageBlobAccessConditions == null ? new PageBlobAccessConditions() - : pageBlobAccessConditions; - if (pageRange == null) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("pageRange cannot be null."); - } - String pageRangeStr = pageRangeToString(pageRange); - - return postProcessResponse(this.azureBlobStorage.pageBlobs().clearPagesWithRestResponseAsync(null, - null, 0, null, pageRangeStr, null, - pageBlobAccessConditions.leaseAccessConditions(), pageBlobAccessConditions.sequenceNumberAccessConditions(), - pageBlobAccessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Returns the list of valid page ranges for a page blob or snapshot of a page blob. - * For more information, see the Azure Docs. - * - * @param blobRange - * {@link BlobRange} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.getPageRanges")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getPageRanges(BlobRange blobRange) { - return this.getPageRanges(blobRange, null); - } - - /** - * Returns the list of valid page ranges for a page blob or snapshot of a page blob. - * For more information, see the Azure Docs. - * - * @param blobRange - * {@link BlobRange} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.getPageRanges")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getPageRanges(BlobRange blobRange, - BlobAccessConditions accessConditions) { - blobRange = blobRange == null ? new BlobRange(0) : blobRange; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.pageBlobs().getPageRangesWithRestResponseAsync( - null, null, snapshot, null, null, blobRange.toHeaderValue(), - null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), - Context.NONE)); - } - - /** - * Gets the collection of page ranges that differ between a specified snapshot and this page blob. - * For more information, see the Azure Docs. - * - * @param blobRange - * {@link BlobRange} - * @param prevSnapshot - * Specifies that the response will contain only pages that were changed between target blob and previous - * snapshot. Changed pages include both updated and cleared pages. The target - * blob may be a snapshot, as long as the snapshot specified by prevsnapshot is the older of the two. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_diff "Sample code for PageBlobAsyncRawClient.getPageRangesDiff")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getPageRangesDiff(BlobRange blobRange, String prevSnapshot) { - return this.getPageRangesDiff(blobRange, prevSnapshot, null); - } - - /** - * Gets the collection of page ranges that differ between a specified snapshot and this page blob. - * For more information, see the Azure Docs. - * - * @param blobRange - * {@link BlobRange} - * @param prevSnapshot - * Specifies that the response will contain only pages that were changed between target blob and previous - * snapshot. Changed pages include both updated and cleared pages. The target - * blob may be a snapshot, as long as the snapshot specified by prevsnapshot is the older of the two. - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code prevSnapshot} is {@code null} - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_diff "Sample code for PageBlobAsyncRawClient.getPageRangesDiff")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getPageRangesDiff(BlobRange blobRange, String prevSnapshot, - BlobAccessConditions accessConditions) { - blobRange = blobRange == null ? new BlobRange(0) : blobRange; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - if (prevSnapshot == null) { - throw new IllegalArgumentException("prevSnapshot cannot be null"); - } - - return postProcessResponse(this.azureBlobStorage.pageBlobs().getPageRangesDiffWithRestResponseAsync( - null, null, snapshot, null, null, prevSnapshot, - blobRange.toHeaderValue(), null, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Resizes the page blob to the specified size (which must be a multiple of 512). - * For more information, see the Azure Docs. - * - * @param size - * Resizes a page blob to the specified size. If the specified value is less than the current size of the - * blob, then all pages above the specified value are cleared. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.resize")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono resize(long size) { - return this.resize(size, null); - } - - /** - * Resizes the page blob to the specified size (which must be a multiple of 512). - * For more information, see the Azure Docs. - * - * @param size - * Resizes a page blob to the specified size. If the specified value is less than the current size of the - * blob, then all pages above the specified value are cleared. - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code size} isn't a multiple of {@link PageBlobAsyncRawClient#PAGE_BYTES} - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.resize")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono resize(long size, BlobAccessConditions accessConditions) { - if (size % PageBlobAsyncRawClient.PAGE_BYTES != 0) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("size must be a multiple of PageBlobAsyncRawClient.PAGE_BYTES."); - } - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.pageBlobs().resizeWithRestResponseAsync(null, - null, size, null, null, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Sets the page blob's sequence number. - * For more information, see the Azure Docs. - * - * @param action - * Indicates how the service should modify the blob's sequence number. - * @param sequenceNumber - * The blob's sequence number. The sequence number is a user-controlled property that you can use to track - * requests and manage concurrency issues. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.updateSequenceNumber")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono updateSequenceNumber(SequenceNumberActionType action, - Long sequenceNumber) { - return this.updateSequenceNumber(action, sequenceNumber, null); - } - - /** - * Sets the page blob's sequence number. - * For more information, see the Azure Docs. - * - * @param action - * Indicates how the service should modify the blob's sequence number. - * @param sequenceNumber - * The blob's sequence number. The sequence number is a user-controlled property that you can use to track - * requests and manage concurrency issues. - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code sequenceNumber} isn't null and is less than 0 - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.updateSequenceNumber")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono updateSequenceNumber(SequenceNumberActionType action, - Long sequenceNumber, BlobAccessConditions accessConditions) { - if (sequenceNumber != null && sequenceNumber < 0) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("SequenceNumber must be greater than or equal to 0."); - } - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - sequenceNumber = action == SequenceNumberActionType.INCREMENT ? null : sequenceNumber; - - return postProcessResponse( - this.azureBlobStorage.pageBlobs().updateSequenceNumberWithRestResponseAsync(null, - null, action, null, sequenceNumber, null, - accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Begins an operation to start an incremental copy from one page blob's snapshot to this page - * blob. The snapshot is copied such that only the differential changes between the previously copied snapshot are - * transferred to the destination. The copied snapshots are complete copies of the original snapshot and can be read - * or copied from as usual. For more information, see - * the Azure Docs here and - * here. - * - * @param source - * The source page blob. - * @param snapshot - * The snapshot on the copy source. - * - * @return Emits the successful response. - */ - public Mono copyIncremental(URL source, String snapshot) { - return this.copyIncremental(source, snapshot, null); - } - - /** - * Begins an operation to start an incremental copy from one page blob's snapshot to this page - * blob. The snapshot is copied such that only the differential changes between the previously copied snapshot are - * transferred to the destination. The copied snapshots are complete copies of the original snapshot and can be read - * or copied from as usual. For more information, see - * the Azure Docs here and - * here. - * - * @param source - * The source page blob. - * @param snapshot - * The snapshot on the copy source. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws Error If {@code source} and {@code snapshot} form a malformed URL. - */ - public Mono copyIncremental(URL source, String snapshot, - ModifiedAccessConditions modifiedAccessConditions) { - - UrlBuilder builder = UrlBuilder.parse(source); - builder.setQueryParameter(Constants.SNAPSHOT_QUERY_PARAMETER, snapshot); - try { - source = builder.toURL(); - } catch (MalformedURLException e) { - // We are parsing a valid url and adding a query parameter. If this fails, we can't recover. - throw new Error(e); - } - return postProcessResponse(this.azureBlobStorage.pageBlobs().copyIncrementalWithRestResponseAsync( - null, null, source, null, null, modifiedAccessConditions, Context.NONE)); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClient.java index 284f74d0216ac..62d2146ea8d61 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClient.java @@ -15,13 +15,14 @@ import com.azure.storage.blob.models.PageRange; import com.azure.storage.blob.models.SequenceNumberActionType; import com.azure.storage.blob.models.SourceModifiedAccessConditions; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import java.io.InputStream; import java.net.URL; -import java.nio.ByteBuffer; import java.time.Duration; /** @@ -45,12 +46,12 @@ public final class PageBlobClient extends BlobClient { /** * Indicates the number of bytes in a page. */ - public static final int PAGE_BYTES = 512; + public static final int PAGE_BYTES = PageBlobAsyncClient.PAGE_BYTES; /** * Indicates the maximum number of bytes that may be sent in a call to putPage. */ - public static final int MAX_PUT_PAGES_BYTES = 4 * Constants.MB; + public static final int MAX_PUT_PAGES_BYTES = PageBlobAsyncClient.MAX_PUT_PAGES_BYTES; /** * Package-private constructor for use by {@link PageBlobClientBuilder}. @@ -189,7 +190,7 @@ public Response uploadPages(PageRange pageRange, InputStream body) public Response uploadPages(PageRange pageRange, InputStream body, PageBlobAccessConditions pageBlobAccessConditions, Duration timeout) { long length = pageRange.end() - pageRange.start(); - Flux fbb = Flux.range(0, (int) Math.ceil((double) length / (double) PAGE_BYTES)) + Flux fbb = Flux.range(0, (int) Math.ceil((double) length / (double) PAGE_BYTES)) .map(i -> i * PAGE_BYTES) .concatMap(pos -> Mono.fromCallable(() -> { byte[] cache = new byte[PAGE_BYTES]; @@ -197,7 +198,8 @@ public Response uploadPages(PageRange pageRange, InputStream body, while (read < PAGE_BYTES) { read += body.read(cache, read, PAGE_BYTES - read); } - return ByteBuffer.wrap(cache); + + return ByteBufAllocator.DEFAULT.buffer(read).writeBytes(cache); })); Mono> response = pageBlobAsyncClient.uploadPages(pageRange, diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java index d5b0aecc3a3f1..cbb5a91600702 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java @@ -8,8 +8,11 @@ import com.azure.core.http.rest.Response; import com.azure.core.http.rest.SimpleResponse; import com.azure.core.http.rest.VoidResponse; +import com.azure.core.util.Context; import com.azure.storage.blob.implementation.AzureBlobStorageBuilder; +import com.azure.storage.blob.implementation.AzureBlobStorageImpl; import com.azure.storage.blob.models.ContainerItem; +import com.azure.storage.blob.models.KeyInfo; import com.azure.storage.blob.models.ListContainersOptions; import com.azure.storage.blob.models.Metadata; import com.azure.storage.blob.models.PublicAccessType; @@ -25,6 +28,8 @@ import java.net.URL; import java.time.OffsetDateTime; +import static com.azure.storage.blob.Utility.postProcessResponse; + /** * Client to a storage account. It may only be instantiated through a {@link StorageClientBuilder}. * This class does not hold any state about a particular storage account but is @@ -47,14 +52,14 @@ * object through {@link Mono#toFuture()}. */ public final class StorageAsyncClient { - StorageAsyncRawClient storageAsyncRawClient; + private final AzureBlobStorageImpl azureBlobStorage; /** * Package-private constructor for use by {@link StorageClientBuilder}. * @param azureBlobStorageBuilder the API client builder for blob storage API */ StorageAsyncClient(AzureBlobStorageBuilder azureBlobStorageBuilder) { - this.storageAsyncRawClient = new StorageAsyncRawClient(azureBlobStorageBuilder.build()); + this.azureBlobStorage = azureBlobStorageBuilder.build(); } /** @@ -69,7 +74,7 @@ public final class StorageAsyncClient { public ContainerAsyncClient getContainerAsyncClient(String containerName) { return new ContainerAsyncClient(new AzureBlobStorageBuilder() .url(Utility.appendToURLPath(getAccountUrl(), containerName).toString()) - .pipeline(storageAsyncRawClient.azureBlobStorage.httpPipeline())); + .pipeline(azureBlobStorage.httpPipeline())); } /** @@ -111,9 +116,9 @@ public Mono> createContainer(String containerName */ public URL getAccountUrl() { try { - return new URL(storageAsyncRawClient.azureBlobStorage.url()); + return new URL(azureBlobStorage.url()); } catch (MalformedURLException e) { - throw new RuntimeException(String.format("Invalid URL on %s: %s" + getClass().getSimpleName(), storageAsyncRawClient.azureBlobStorage.url()), e); + throw new RuntimeException(String.format("Invalid URL on %s: %s" + getClass().getSimpleName(), azureBlobStorage.url()), e); } } @@ -139,17 +144,46 @@ public Flux listContainers() { * A reactive response emitting the list of containers. */ public Flux listContainers(ListContainersOptions options) { - return storageAsyncRawClient - .listContainersSegment(null, options) + return listContainersSegment(null, options) .flatMapMany(response -> listContainersHelper(response.value().marker(), options, response)); } + /* + * Returns a Mono segment of containers starting from the specified Marker. + * Use an empty marker to start enumeration from the beginning. Container names are returned in lexicographic order. + * After getting a segment, process it, and then call ListContainers again (passing the the previously-returned + * Marker) to get the next segment. For more information, see + * the Azure Docs. + * + * @param marker + * Identifies the portion of the list to be returned with the next list operation. + * This value is returned in the response of a previous list operation as the + * ListContainersSegmentResponse.body().nextMarker(). Set to null to list the first segment. + * @param options + * A {@link ListContainersOptions} which specifies what data should be returned by the service. + * + * @return Emits the successful response. + * + * @apiNote ## Sample Code \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list "Sample code for ServiceURL.listContainersSegment")] \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list_helper "Helper code for ServiceURL.listContainersSegment")] \n + * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) + */ + private Mono listContainersSegment(String marker, ListContainersOptions options) { + options = options == null ? new ListContainersOptions() : options; + + return postProcessResponse( + this.azureBlobStorage.services().listContainersSegmentWithRestResponseAsync( + options.prefix(), marker, options.maxResults(), options.details().toIncludeType(), null, + null, Context.NONE)); + } + private Flux listContainersHelper(String marker, ListContainersOptions options, ServicesListContainersSegmentResponse response) { Flux result = Flux.fromIterable(response.value().containerItems()); if (response.value().nextMarker() != null) { // Recursively add the continuation items to the observable. - result = result.concatWith(storageAsyncRawClient.listContainersSegment(marker, options) + result = result.concatWith(listContainersSegment(marker, options) .flatMapMany((r) -> listContainersHelper(response.value().nextMarker(), options, r))); } @@ -165,8 +199,8 @@ private Flux listContainersHelper(String marker, ListContainersOp * A reactive response containing the storage account properties. */ public Mono> getProperties() { - return storageAsyncRawClient - .getProperties() + return postProcessResponse( + this.azureBlobStorage.services().getPropertiesWithRestResponseAsync(null, null, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.value())); } @@ -183,8 +217,8 @@ public Mono> getProperties() { * A reactive response containing the storage account properties. */ public Mono setProperties(StorageServiceProperties properties) { - return storageAsyncRawClient - .setProperties(properties) + return postProcessResponse( + this.azureBlobStorage.services().setPropertiesWithRestResponseAsync(properties, null, null, Context.NONE)) .map(VoidResponse::new); } @@ -197,13 +231,22 @@ public Mono setProperties(StorageServiceProperties properties) { * @param expiry * Expiration of the key's validity. * - * @return - * A reactive response containing the user delegation key. + * @return A reactive response containing the user delegation key. + * @throws IllegalArgumentException If {@code start} isn't null and is after {@code expiry}. */ public Mono> getUserDelegationKey(OffsetDateTime start, OffsetDateTime expiry) { - return storageAsyncRawClient - .getUserDelegationKey(start, expiry) - .map(rb -> new SimpleResponse<>(rb, rb.value())); + Utility.assertNotNull("expiry", expiry); + if (start != null && !start.isBefore(expiry)) { + throw new IllegalArgumentException("`start` must be null or a datetime before `expiry`."); + } + + return postProcessResponse( + this.azureBlobStorage.services().getUserDelegationKeyWithRestResponseAsync( + new KeyInfo() + .start(start == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(start)) + .expiry(Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiry)), + null, null, Context.NONE) + ).map(rb -> new SimpleResponse<>(rb, rb.value())); } /** @@ -216,8 +259,8 @@ public Mono> getUserDelegationKey(OffsetDateTime sta * A reactive response containing the storage account statistics. */ public Mono> getStatistics() { - return storageAsyncRawClient - .getStatistics() + return postProcessResponse( + this.azureBlobStorage.services().getStatisticsWithRestResponseAsync(null, null, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.value())); } @@ -229,8 +272,7 @@ public Mono> getStatistics() { * A reactive response containing the storage account info. */ public Mono> getAccountInfo() { - return storageAsyncRawClient - .getAccountInfo() + return postProcessResponse(this.azureBlobStorage.services().getAccountInfoWithRestResponseAsync(Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new StorageAccountInfo(rb.deserializedHeaders()))); } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncRawClient.java deleted file mode 100644 index a2b6be965ed30..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncRawClient.java +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.credentials.TokenCredential; -import com.azure.core.http.HttpPipeline; -import com.azure.core.util.Context; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.KeyInfo; -import com.azure.storage.blob.models.ListContainersOptions; -import com.azure.storage.blob.models.ServicesGetAccountInfoResponse; -import com.azure.storage.blob.models.ServicesGetPropertiesResponse; -import com.azure.storage.blob.models.ServicesGetStatisticsResponse; -import com.azure.storage.blob.models.ServicesGetUserDelegationKeyResponse; -import com.azure.storage.blob.models.ServicesListContainersSegmentResponse; -import com.azure.storage.blob.models.ServicesSetPropertiesResponse; -import com.azure.storage.blob.models.StorageServiceProperties; -import reactor.core.publisher.Mono; - -import java.time.OffsetDateTime; - -import static com.azure.storage.blob.Utility.postProcessResponse; - -/** - * Represents a URL to a storage service. This class does not hold any state about a particular storage account but is - * instead a convenient way of sending off appropriate requests to the resource on the service. - * It may also be used to construct URLs to blobs and containers. - * Please see here for more - * information on containers. - */ -final class StorageAsyncRawClient { - - final AzureBlobStorageImpl azureBlobStorage; - - /** - * Creates a {@code ServiceURL} object pointing to the account specified by the URL and using the provided pipeline - * to make HTTP requests. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_url "Sample code for ServiceURL constructor")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - StorageAsyncRawClient(AzureBlobStorageImpl azureBlobStorage) { - this.azureBlobStorage = azureBlobStorage; - } - - /** - * Returns a Mono segment of containers starting from the specified Marker. - * Use an empty marker to start enumeration from the beginning. Container names are returned in lexicographic order. - * After getting a segment, process it, and then call ListContainers again (passing the the previously-returned - * Marker) to get the next segment. For more information, see - * the Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListContainersSegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param options - * A {@link ListContainersOptions} which specifies what data should be returned by the service. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list "Sample code for ServiceURL.listContainersSegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list_helper "Helper code for ServiceURL.listContainersSegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono listContainersSegment(String marker, - ListContainersOptions options) { - options = options == null ? new ListContainersOptions() : options; - - return postProcessResponse( - this.azureBlobStorage.services().listContainersSegmentWithRestResponseAsync( - options.prefix(), marker, options.maxResults(), options.details().toIncludeType(), null, - null, Context.NONE)); - } - - /** - * Gets the properties of a storage account’s Blob service. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_getsetprops "Sample code for ServiceURL.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getProperties() { - return postProcessResponse( - this.azureBlobStorage.services().getPropertiesWithRestResponseAsync(null, null, Context.NONE)); - } - - /** - * Sets properties for a storage account's Blob service endpoint. For more information, see the - * Azure Docs. - * Note that setting the default service version has no effect when using this client because this client explicitly - * sets the version header on each request, overriding the default. - * - * @param properties - * Configures the service. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_getsetprops "Sample code for ServiceURL.setProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setProperties(StorageServiceProperties properties) { - return postProcessResponse( - this.azureBlobStorage.services().setPropertiesWithRestResponseAsync(properties, null, null, Context.NONE)); - } - - /** - * Gets a user delegation key for use with this account's blob storage. - * Note: This method call is only valid when using {@link TokenCredential} in this object's {@link HttpPipeline}. - * - * @param start - * Start time for the key's validity. Null indicates immediate start. - * @param expiry - * Expiration of the key's validity. - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code start} isn't null and is after {@code expiry}. - */ - public Mono getUserDelegationKey(OffsetDateTime start, OffsetDateTime expiry) { - Utility.assertNotNull("expiry", expiry); - if (start != null && !start.isBefore(expiry)) { - throw new IllegalArgumentException("`start` must be null or a datetime before `expiry`."); - } - - return postProcessResponse( - this.azureBlobStorage.services().getUserDelegationKeyWithRestResponseAsync( - new KeyInfo() - .start(start == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(start)) - .expiry(Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiry)), - null, null, Context.NONE) - ); - } - - /** - * Retrieves statistics related to replication for the Blob service. It is only available on the secondary - * location endpoint when read-access geo-redundant replication is enabled for the storage account. For more - * information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_stats "Sample code for ServiceURL.getStats")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getStatistics() { - return postProcessResponse( - this.azureBlobStorage.services().getStatisticsWithRestResponseAsync(null, null, Context.NONE)); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for ServiceURL.getAccountInfo")] \n - * For more samples, please see the [Samples file] (https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getAccountInfo() { - return postProcessResponse( - this.azureBlobStorage.services().getAccountInfoWithRestResponseAsync(Context.NONE)); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClient.java index f18cf0640fbd9..cc4c6b71c56fa 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClient.java @@ -38,8 +38,7 @@ * information on containers. */ public final class StorageClient { - - private StorageAsyncClient storageAsyncClient; + private final StorageAsyncClient storageAsyncClient; /** * Package-private constructor for use by {@link StorageClientBuilder}. diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageRawClient.java deleted file mode 100644 index cebfdc451ae67..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageRawClient.java +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.http.HttpPipeline; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.ListContainersOptions; -import com.azure.storage.blob.models.ServicesGetAccountInfoResponse; -import com.azure.storage.blob.models.ServicesGetPropertiesResponse; -import com.azure.storage.blob.models.ServicesGetStatisticsResponse; -import com.azure.storage.blob.models.ServicesGetUserDelegationKeyResponse; -import com.azure.storage.blob.models.ServicesListContainersSegmentResponse; -import com.azure.storage.blob.models.ServicesSetPropertiesResponse; -import com.azure.storage.blob.models.StorageServiceProperties; -import com.azure.storage.common.credentials.SASTokenCredential; -import reactor.core.publisher.Mono; - -import java.time.Duration; -import java.time.OffsetDateTime; - -/** - * Represents a URL to a storage service. This class does not hold any state about a particular storage account but is - * instead a convenient way of sending off appropriate requests to the resource on the service. - * It may also be used to construct URLs to blobs and containers. - * Please see here for more - * information on containers. - */ -final class StorageRawClient { - - StorageAsyncRawClient storageAsyncRawClient; - - /** - * Creates a {@code ServiceURL} object pointing to the account specified by the URL and using the provided pipeline - * to make HTTP requests. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_url "Sample code for ServiceURL constructor")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - StorageRawClient(AzureBlobStorageImpl azureBlobStorage) { - this.storageAsyncRawClient = new StorageAsyncRawClient(azureBlobStorage); - } - - /** - * Returns a Mono segment of containers starting from the specified Marker. - * Use an empty marker to start enumeration from the beginning. Container names are returned in lexicographic order. - * After getting a segment, process it, and then call ListContainers again (passing the the previously-returned - * Marker) to get the next segment. For more information, see - * the Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListContainersSegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param options - * A {@link ListContainersOptions} which specifies what data should be returned by the service. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list "Sample code for ServiceURL.listContainersSegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list_helper "Helper code for ServiceURL.listContainersSegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesListContainersSegmentResponse listContainersSegment(String marker, - ListContainersOptions options) { - return this.listContainersSegment(marker, options, null); - } - - /** - * Returns a Mono segment of containers starting from the specified Marker. - * Use an empty marker to start enumeration from the beginning. Container names are returned in lexicographic order. - * After getting a segment, process it, and then call ListContainers again (passing the the previously-returned - * Marker) to get the next segment. For more information, see - * the Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListContainersSegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param options - * A {@link ListContainersOptions} which specifies what data should be returned by the service. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list "Sample code for ServiceURL.listContainersSegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list_helper "Helper code for ServiceURL.listContainersSegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesListContainersSegmentResponse listContainersSegment(String marker, - ListContainersOptions options, Duration timeout) { - Mono response = storageAsyncRawClient.listContainersSegment(marker, options); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Gets the properties of a storage account’s Blob service. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_getsetprops "Sample code for ServiceURL.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesGetPropertiesResponse getProperties() { - return this.getProperties(null); - } - - /** - * Gets the properties of a storage account’s Blob service. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_getsetprops "Sample code for ServiceURL.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesGetPropertiesResponse getProperties(Duration timeout) { - Mono response = storageAsyncRawClient.getProperties(); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Sets properties for a storage account's Blob service endpoint. For more information, see the - * Azure Docs. - * Note that setting the default service version has no effect when using this client because this client explicitly - * sets the version header on each request, overriding the default. - * - * @param properties - * Configures the service. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_getsetprops "Sample code for ServiceURL.setProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesSetPropertiesResponse setProperties(StorageServiceProperties properties) { - return this.setProperties(properties, null); - } - - /** - * Sets properties for a storage account's Blob service endpoint. For more information, see the - * Azure Docs. - * Note that setting the default service version has no effect when using this client because this client explicitly - * sets the version header on each request, overriding the default. - * - * @param properties - * Configures the service. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_getsetprops "Sample code for ServiceURL.setProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesSetPropertiesResponse setProperties(StorageServiceProperties properties, Duration timeout) { - Mono response = storageAsyncRawClient.setProperties(properties); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Gets a user delegation key for use with this account's blob storage. - * Note: This method call is only valid when using {@link SASTokenCredential} in this object's {@link HttpPipeline}. - * - * @param start - * Start time for the key's validity. Null indicates immediate start. - * @param expiry - * Expiration of the key's validity. - * - * @return Emits the successful response. - */ - public ServicesGetUserDelegationKeyResponse getUserDelegationKey(OffsetDateTime start, OffsetDateTime expiry) { - return this.getUserDelegationKey(start, expiry, null); - } - - /** - * Gets a user delegation key for use with this account's blob storage. - * Note: This method call is only valid when using {@link SASTokenCredential} in this object's {@link HttpPipeline}. - * - * @param start - * Start time for the key's validity. Null indicates immediate start. - * @param expiry - * Expiration of the key's validity. - * - * @return Emits the successful response. - */ - public ServicesGetUserDelegationKeyResponse getUserDelegationKey(OffsetDateTime start, OffsetDateTime expiry, - Duration timeout) { - Mono response = storageAsyncRawClient.getUserDelegationKey(start, expiry); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Retrieves statistics related to replication for the Blob service. It is only available on the secondary - * location endpoint when read-access geo-redundant replication is enabled for the storage account. For more - * information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_stats "Sample code for ServiceURL.getStats")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesGetStatisticsResponse getStatistics() { - return this.getStatistics(null); - } - - /** - * Retrieves statistics related to replication for the Blob service. It is only available on the secondary - * location endpoint when read-access geo-redundant replication is enabled for the storage account. For more - * information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_stats "Sample code for ServiceURL.getStats")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesGetStatisticsResponse getStatistics(Duration timeout) { - Mono response = storageAsyncRawClient.getStatistics(); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for ServiceURL.getAccountInfo")] \n - * For more samples, please see the [Samples file] (https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesGetAccountInfoResponse getAccountInfo() { - return this.getAccountInfo(null); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for ServiceURL.getAccountInfo")] \n - * For more samples, please see the [Samples file] (https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesGetAccountInfoResponse getAccountInfo(Duration timeout) { - Mono response = storageAsyncRawClient.getAccountInfo(); - return Utility.blockWithOptionalTimeout(response, timeout); - } -} diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/Sample.java b/storage/client/blob/src/test/java/com/azure/storage/blob/Sample.java index 1ac12cbe978da..f6d19d6b84167 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/Sample.java +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/Sample.java @@ -8,6 +8,8 @@ import com.azure.storage.blob.models.BlobItem; import com.azure.storage.blob.models.ContainerItem; import com.azure.storage.common.credentials.SharedKeyCredential; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -16,7 +18,6 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.UUID; @@ -116,8 +117,7 @@ public void asyncSample() throws IOException { for (int i = 0; i < 5; i++) { BlockBlobAsyncClient blobClient = finalContainerClient.getBlockBlobAsyncClient("testblob-" + i); byte[] message = ("test data" + i).getBytes(StandardCharsets.UTF_8); - Flux testdata = Flux.just(ByteBuffer.wrap(message)); - + Flux testdata = Flux.just(ByteBufAllocator.DEFAULT.buffer(message.length).writeBytes(message)); finished = finished.and(blobClient.upload(testdata, message.length) .then(Mono.defer(() -> { From 5493c38318e7f8d7348750ff26c16fe8300a1bdd Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Thu, 11 Jul 2019 10:24:02 -0700 Subject: [PATCH 02/19] Fixed PageBlob getRange and getRangeDiff, turned on all unit tests --- .../storage/blob/PageBlobAsyncClient.java | 13 +-- .../azure/storage/blob/PageBlobClient.java | 17 ++-- .../com/azure/storage/blob/AadLoginTest.java | 4 +- .../azure/storage/blob/PageBlobAPITest.groovy | 97 +++++++++---------- 4 files changed, 64 insertions(+), 67 deletions(-) diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncClient.java index 72f052fd27573..7c602e4b2608b 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncClient.java @@ -16,6 +16,7 @@ import com.azure.storage.blob.models.ModifiedAccessConditions; import com.azure.storage.blob.models.PageBlobAccessConditions; import com.azure.storage.blob.models.PageBlobItem; +import com.azure.storage.blob.models.PageList; import com.azure.storage.blob.models.PageRange; import com.azure.storage.blob.models.SequenceNumberActionType; import com.azure.storage.blob.models.SourceModifiedAccessConditions; @@ -336,7 +337,7 @@ public Mono> clearPages(PageRange pageRange, * @return * A reactive response containing the information of the cleared pages. */ - public Flux getPageRanges(BlobRange blobRange) { + public Mono> getPageRanges(BlobRange blobRange) { return this.getPageRanges(blobRange, null); } @@ -352,7 +353,7 @@ public Flux getPageRanges(BlobRange blobRange) { * @return * A reactive response emitting all the page ranges. */ - public Flux getPageRanges(BlobRange blobRange, BlobAccessConditions accessConditions) { + public Mono> getPageRanges(BlobRange blobRange, BlobAccessConditions accessConditions) { blobRange = blobRange == null ? new BlobRange(0) : blobRange; accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; @@ -360,7 +361,7 @@ public Flux getPageRanges(BlobRange blobRange, BlobAccessConditions a null, null, snapshot, null, null, blobRange.toHeaderValue(), null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) - .flatMapMany(response -> Flux.fromIterable(response.value().pageRange())); + .map(response -> new SimpleResponse<>(response, response.value())); } /** @@ -377,7 +378,7 @@ public Flux getPageRanges(BlobRange blobRange, BlobAccessConditions a * @return * A reactive response emitting all the different page ranges. */ - public Flux getPageRangesDiff(BlobRange blobRange, String prevSnapshot) { + public Mono> getPageRangesDiff(BlobRange blobRange, String prevSnapshot) { return this.getPageRangesDiff(blobRange, prevSnapshot, null); } @@ -397,7 +398,7 @@ public Flux getPageRangesDiff(BlobRange blobRange, String prevSnapsho * @return A reactive response emitting all the different page ranges. * @throws IllegalArgumentException If {@code prevSnapshot} is {@code null} */ - public Flux getPageRangesDiff(BlobRange blobRange, String prevSnapshot, BlobAccessConditions accessConditions) { + public Mono> getPageRangesDiff(BlobRange blobRange, String prevSnapshot, BlobAccessConditions accessConditions) { blobRange = blobRange == null ? new BlobRange(0) : blobRange; accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; @@ -409,7 +410,7 @@ public Flux getPageRangesDiff(BlobRange blobRange, String prevSnapsho null, null, snapshot, null, null, prevSnapshot, blobRange.toHeaderValue(), null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) - .flatMapMany(response -> Flux.fromIterable(response.value().pageRange())); + .map(response -> new SimpleResponse<>(response, response.value())); } /** diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClient.java index 62d2146ea8d61..ed294b73b1e26 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClient.java @@ -12,6 +12,7 @@ import com.azure.storage.blob.models.ModifiedAccessConditions; import com.azure.storage.blob.models.PageBlobAccessConditions; import com.azure.storage.blob.models.PageBlobItem; +import com.azure.storage.blob.models.PageList; import com.azure.storage.blob.models.PageRange; import com.azure.storage.blob.models.SequenceNumberActionType; import com.azure.storage.blob.models.SourceModifiedAccessConditions; @@ -325,7 +326,7 @@ public Response clearPages(PageRange pageRange, * @return * The information of the cleared pages. */ - public Iterable getPageRanges(BlobRange blobRange) { + public Response getPageRanges(BlobRange blobRange) { return this.getPageRanges(blobRange, null, null); } @@ -343,10 +344,8 @@ public Iterable getPageRanges(BlobRange blobRange) { * @return * All the page ranges. */ - public Iterable getPageRanges(BlobRange blobRange, - BlobAccessConditions accessConditions, Duration timeout) { - Flux response = pageBlobAsyncClient.getPageRanges(blobRange, accessConditions); - return timeout == null ? response.toIterable() : response.timeout(timeout).toIterable(); + public Response getPageRanges(BlobRange blobRange, BlobAccessConditions accessConditions, Duration timeout) { + return Utility.blockWithOptionalTimeout(pageBlobAsyncClient.getPageRanges(blobRange, accessConditions), timeout); } /** @@ -363,7 +362,7 @@ public Iterable getPageRanges(BlobRange blobRange, * @return * All the different page ranges. */ - public Iterable getPageRangesDiff(BlobRange blobRange, String prevSnapshot) { + public Response getPageRangesDiff(BlobRange blobRange, String prevSnapshot) { return this.getPageRangesDiff(blobRange, prevSnapshot, null, null); } @@ -385,10 +384,8 @@ public Iterable getPageRangesDiff(BlobRange blobRange, String prevSna * @return * All the different page ranges. */ - public Iterable getPageRangesDiff(BlobRange blobRange, String prevSnapshot, - BlobAccessConditions accessConditions, Duration timeout) { - Flux response = pageBlobAsyncClient.getPageRangesDiff(blobRange, prevSnapshot, accessConditions); - return timeout == null ? response.toIterable() : response.timeout(timeout).toIterable(); + public Response getPageRangesDiff(BlobRange blobRange, String prevSnapshot, BlobAccessConditions accessConditions, Duration timeout) { + return Utility.blockWithOptionalTimeout(pageBlobAsyncClient.getPageRangesDiff(blobRange, prevSnapshot, accessConditions), timeout); } /** diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/AadLoginTest.java b/storage/client/blob/src/test/java/com/azure/storage/blob/AadLoginTest.java index 3c2ee324d1967..82311efd423ff 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/AadLoginTest.java +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/AadLoginTest.java @@ -3,6 +3,7 @@ package com.azure.storage.blob; +import com.azure.core.util.configuration.ConfigurationManager; import com.azure.identity.credential.EnvironmentCredential; import com.azure.storage.blob.models.ContainerItem; import org.junit.BeforeClass; @@ -15,8 +16,9 @@ public class AadLoginTest { @BeforeClass public static void setup() { + String endpoint = String.format("https://%s.blob.core.windows.net", ConfigurationManager.getConfiguration().get("PRIMARY_STORAGE_ACCOUNT_KEY")); storageClient = new StorageClientBuilder() - .endpoint("https://" + System.getenv("ACCOUNT_NAME") + ".blob.core.windows.net") + .endpoint(endpoint) .credential(new EnvironmentCredential()) // .httpClient(HttpClient.createDefault().proxy(() -> new ProxyOptions(Type.HTTP, new InetSocketAddress("localhost", 8888)))) .buildClient(); diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/PageBlobAPITest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/PageBlobAPITest.groovy index eee346b007288..333fee7936084 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/PageBlobAPITest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/PageBlobAPITest.groovy @@ -40,7 +40,7 @@ class PageBlobAPITest extends APISpec { def "Create sequence number"() { when: bu.create(PageBlobClient.PAGE_BYTES, 2, null, null, - null, null) + null, null) then: Integer.parseInt(bu.getProperties().headers().value("x-ms-blob-sequence-number")) == 2 @@ -50,11 +50,11 @@ class PageBlobAPITest extends APISpec { def "Create headers"() { setup: BlobHTTPHeaders headers = new BlobHTTPHeaders().blobCacheControl(cacheControl) - .blobContentDisposition(contentDisposition) - .blobContentEncoding(contentEncoding) - .blobContentLanguage(contentLanguage) - .blobContentMD5(contentMD5) - .blobContentType(contentType) + .blobContentDisposition(contentDisposition) + .blobContentEncoding(contentEncoding) + .blobContentLanguage(contentLanguage) + .blobContentMD5(contentMD5) + .blobContentType(contentType) when: bu.create(PageBlobClient.PAGE_BYTES, null, headers, null, null, null) @@ -67,7 +67,7 @@ class PageBlobAPITest extends APISpec { where: cacheControl | contentDisposition | contentEncoding | contentLanguage | contentMD5 | contentType null | null | null | null | null | null - // "control" | "disposition" | "encoding" | "language" | Base64.getEncoder().encode(MessageDigest.getInstance("MD5").digest(defaultData.array())) | "type" TODO (alzimmer): Determine why getProperties returns null + "control" | "disposition" | "encoding" | "language" | Base64.getEncoder().encode(MessageDigest.getInstance("MD5").digest(defaultData.array())) | "type" } @Unroll @@ -109,7 +109,7 @@ class PageBlobAPITest extends APISpec { expect: bu.create(PageBlobClient.PAGE_BYTES, null, null, null, bac, null) - .statusCode() == 201 + .statusCode() == 201 where: modified | unmodified | match | noneMatch | leaseID @@ -159,7 +159,7 @@ class PageBlobAPITest extends APISpec { def "Upload page"() { when: Response response = bu.uploadPages(new PageRange().start(0).end(PageBlobClient.PAGE_BYTES - 1), - new ByteArrayInputStream(getRandomByteArray(PageBlobClient.PAGE_BYTES))) + new ByteArrayInputStream(getRandomByteArray(PageBlobClient.PAGE_BYTES))) then: response.statusCode() == 201 @@ -306,7 +306,7 @@ class PageBlobAPITest extends APISpec { when: destURL.uploadPagesFromURL(new PageRange().start(0).end(PageBlobClient.PAGE_BYTES * 2 - 1), - sourceURL.getBlobUrl(), PageBlobClient.PAGE_BYTES * 2) + sourceURL.getBlobUrl(), PageBlobClient.PAGE_BYTES * 2) then: ByteArrayOutputStream outputStream = new ByteArrayOutputStream() @@ -323,8 +323,8 @@ class PageBlobAPITest extends APISpec { thrown(IllegalArgumentException) where: - sourceOffset | range - (Long)PageBlobClient.PAGE_BYTES | null + sourceOffset | range + (Long) PageBlobClient.PAGE_BYTES | null } def "Upload page from URL MD5"() { @@ -354,14 +354,14 @@ class PageBlobAPITest extends APISpec { when: destURL.uploadPagesFromURL(pageRange, bu.getBlobUrl(), null, - MessageDigest.getInstance("MD5").digest("garbage".getBytes()), null, null, null) + MessageDigest.getInstance("MD5").digest("garbage".getBytes()), null, null, null) then: thrown(StorageException) } @Unroll - def "Upload page from URL destination AC"() { + def "Upload page from URL destination AC"() { setup: cu.setAccessPolicy(PublicAccessType.CONTAINER, null, null, null) def sourceURL = cu.getPageBlobClient(generateBlobName()) @@ -504,7 +504,7 @@ class PageBlobAPITest extends APISpec { Response response = bu.clearPages(new PageRange().start(0).end(PageBlobClient.PAGE_BYTES - 1)) then: - !bu.getPageRanges(new BlobRange(0)).iterator().hasNext() + bu.getPageRanges(new BlobRange(0)).value().pageRange().size() == 0 validateBasicHeaders(response.headers()) response.value().contentMD5() == null response.value().blobSequenceNumber() == 0 @@ -536,7 +536,7 @@ class PageBlobAPITest extends APISpec { expect: bu.clearPages(new PageRange().start(0).end(PageBlobClient.PAGE_BYTES - 1), pac, null) - .statusCode() == 201 + .statusCode() == 201 where: modified | unmodified | match | noneMatch | leaseID | sequenceNumberLT | sequenceNumberLTE | sequenceNumberEqual @@ -600,21 +600,20 @@ class PageBlobAPITest extends APISpec { thrown(StorageException) } - // TODO (alzimmer): Turn this on once paged responses become available - /*def "Get page ranges"() { + def "Get page ranges"() { setup: bu.uploadPages(new PageRange().start(0).end(PageBlobClient.PAGE_BYTES - 1), new ByteArrayInputStream(getRandomByteArray(PageBlobClient.PAGE_BYTES))) when: - Iterable response = bu.getPageRanges(new BlobRange(0, PageBlobClient.PAGE_BYTES)) + Response response = bu.getPageRanges(new BlobRange(0, PageBlobClient.PAGE_BYTES)) then: response.statusCode() == 200 - response.body().pageRange().size() == 1 - validateBasicHeaders(headers) - headers.blobContentLength() == (long) PageBlobClient.PAGE_BYTES - }*/ + response.value().pageRange().size() == 1 + validateBasicHeaders(response.headers()) + Integer.parseInt(response.headers().value("x-ms-blob-content-length")) == PageBlobClient.PAGE_BYTES + } def "Get page ranges min"() { when: @@ -639,7 +638,7 @@ class PageBlobAPITest extends APISpec { when: - bu.getPageRanges(new BlobRange(0, PageBlobClient.PAGE_BYTES), bac, null).iterator().hasNext() + bu.getPageRanges(new BlobRange(0, PageBlobClient.PAGE_BYTES), bac, null) then: notThrown(StorageException) @@ -666,7 +665,7 @@ class PageBlobAPITest extends APISpec { .ifNoneMatch(setupBlobMatchCondition(bu, noneMatch))) when: - bu.getPageRanges(new BlobRange(0, PageBlobClient.PAGE_BYTES), bac, null).iterator().hasNext() + bu.getPageRanges(new BlobRange(0, PageBlobClient.PAGE_BYTES), bac, null) then: thrown(StorageException) @@ -685,14 +684,13 @@ class PageBlobAPITest extends APISpec { bu = cu.getPageBlobClient(generateBlobName()) when: - bu.getPageRanges(null).iterator().hasNext() + bu.getPageRanges(null) then: thrown(StorageException) } - // TODO (alzimmer): This test needs to be restructured to support the Iterable T return - /*def "Get page ranges diff"() { + def "Get page ranges diff"() { setup: bu.create(PageBlobClient.PAGE_BYTES * 2) @@ -707,19 +705,18 @@ class PageBlobAPITest extends APISpec { bu.clearPages(new PageRange().start(PageBlobClient.PAGE_BYTES).end(PageBlobClient.PAGE_BYTES * 2 - 1)) when: - Iterable response = bu.getPageRangesDiff(new BlobRange(0, PageBlobClient.PAGE_BYTES * 2), snapshot) - PageBlobGetPageRangesDiffHeaders headers = response.headers() + Response response = bu.getPageRangesDiff(new BlobRange(0, PageBlobClient.PAGE_BYTES * 2), snapshot) then: - response.body().pageRange().size() == 1 - response.body().pageRange().get(0).start() == 0 - response.body().pageRange().get(0).end() == PageBlobClient.PAGE_BYTES - 1 - response.body().clearRange().size() == 1 - response.body().clearRange().get(0).start() == PageBlobClient.PAGE_BYTES - response.body().clearRange().get(0).end() == PageBlobClient.PAGE_BYTES * 2 - 1 - validateBasicHeaders(headers) - headers.blobContentLength() == PageBlobClient.PAGE_BYTES * 2 - }*/ + response.value().pageRange().size() == 1 + response.value().pageRange().get(0).start() == 0 + response.value().pageRange().get(0).end() == PageBlobClient.PAGE_BYTES - 1 + response.value().clearRange().size() == 1 + response.value().clearRange().get(0).start() == PageBlobClient.PAGE_BYTES + response.value().clearRange().get(0).end() == PageBlobClient.PAGE_BYTES * 2 - 1 + validateBasicHeaders(response.headers()) + Integer.parseInt(response.headers().value("x-ms-blob-content-length")) == PageBlobClient.PAGE_BYTES * 2 + } def "Get page ranges diff min"() { setup: @@ -745,7 +742,7 @@ class PageBlobAPITest extends APISpec { .ifNoneMatch(noneMatch)) when: - bu.getPageRangesDiff(new BlobRange(0, PageBlobClient.PAGE_BYTES), snapshot, bac, null).iterator().hasNext() + bu.getPageRangesDiff(new BlobRange(0, PageBlobClient.PAGE_BYTES), snapshot, bac, null) then: notThrown(StorageException) @@ -774,7 +771,7 @@ class PageBlobAPITest extends APISpec { .ifNoneMatch(setupBlobMatchCondition(bu, noneMatch))) when: - bu.getPageRangesDiff(new BlobRange(0, PageBlobClient.PAGE_BYTES), snapshot, bac, null).iterator().hasNext() + bu.getPageRangesDiff(new BlobRange(0, PageBlobClient.PAGE_BYTES), snapshot, bac, null) then: thrown(StorageException) @@ -793,7 +790,7 @@ class PageBlobAPITest extends APISpec { bu = cu.getPageBlobClient(generateBlobName()) when: - bu.getPageRangesDiff(null, "snapshot").iterator().hasNext() + bu.getPageRangesDiff(null, "snapshot") then: thrown(StorageException) @@ -811,13 +808,13 @@ class PageBlobAPITest extends APISpec { thrown(IllegalArgumentException) where: - start | end - 1 | 1 - -PageBlobClient.PAGE_BYTES | PageBlobClient.PAGE_BYTES - 1 - 0 | 0 - 1 | PageBlobClient.PAGE_BYTES - 1 - 0 | PageBlobClient.PAGE_BYTES - PageBlobClient.PAGE_BYTES * 2 | PageBlobClient.PAGE_BYTES - 1 + start | end + 1 | 1 + -PageBlobClient.PAGE_BYTES | PageBlobClient.PAGE_BYTES - 1 + 0 | 0 + 1 | PageBlobClient.PAGE_BYTES - 1 + 0 | PageBlobClient.PAGE_BYTES + PageBlobClient.PAGE_BYTES * 2 | PageBlobClient.PAGE_BYTES - 1 } def "Resize"() { @@ -931,7 +928,7 @@ class PageBlobAPITest extends APISpec { expect: bu.updateSequenceNumber(SequenceNumberActionType.UPDATE, 1, bac, null) - .statusCode() == 200 + .statusCode() == 200 where: modified | unmodified | match | noneMatch | leaseID From 64643abeaa5db5a2afe0435c2c4a85b437a7cf55 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Thu, 11 Jul 2019 11:21:07 -0700 Subject: [PATCH 03/19] Turned on more tests --- .../com/azure/storage/blob/APISpec.groovy | 9 +-------- .../storage/blob/AppendBlobAPITest.groovy | 3 +++ .../com/azure/storage/blob/BlobAPITest.groovy | 13 ++++++------ .../storage/blob/BlockBlobAPITest.groovy | 20 ++++++++++++------- .../azure/storage/blob/PageBlobAPITest.groovy | 3 +++ 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy index c59095e5f66c6..8eb35ff82eb8d 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy @@ -7,7 +7,6 @@ import com.azure.core.http.* import com.azure.core.http.policy.HttpLogDetailLevel import com.azure.core.http.policy.HttpPipelinePolicy import com.azure.core.http.rest.Response -import com.azure.core.util.Context import com.azure.core.util.configuration.ConfigurationManager import com.azure.identity.credential.EnvironmentCredential import com.azure.storage.blob.BlobProperties @@ -42,8 +41,6 @@ class APISpec extends Specification { static final ByteBuffer defaultData = ByteBuffer.wrap(defaultText.getBytes(StandardCharsets.UTF_8)) - static final Flux defaultFlux = Flux.just(defaultData) - static final Supplier defaultInputStream = new Supplier() { @Override InputStream get() { @@ -112,10 +109,6 @@ class APISpec extends Specification { */ static final String defaultContextKey = "Key" - static final String defaultContextValue = "Value" - - static final Context defaultContext = new Context(defaultContextKey, defaultContextValue) - static String getTestName(ISpecificationContext ctx) { return ctx.getCurrentFeature().name.replace(' ', '').toLowerCase() } @@ -409,7 +402,7 @@ class APISpec extends Specification { response.value().contentEncoding() == contentEncoding && response.value().contentLanguage() == contentLanguage && response.value().contentMD5() == contentMD5 && - response.headers().value("Content-Type") == (contentType == null ? "application/octet-stream" : contentType) + response.headers().value("Content-Type") == contentType } static Metadata getMetadataFromHeaders(HttpHeaders headers) { diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/AppendBlobAPITest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/AppendBlobAPITest.groovy index b295c16c976d9..5d6f364f7e5e7 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/AppendBlobAPITest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/AppendBlobAPITest.groovy @@ -57,6 +57,9 @@ class AppendBlobAPITest extends APISpec { bu.create(headers, null, null, null) Response response = bu.getProperties() + // If the value isn't set the service will automatically set it + contentType = (contentType == null) ? "application/octet-stream" : contentType + then: validateBlobProperties(response, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentMD5, contentType) diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/BlobAPITest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/BlobAPITest.groovy index 0cf250d85d174..c284a321b84d2 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/BlobAPITest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/BlobAPITest.groovy @@ -359,8 +359,7 @@ class BlobAPITest extends APISpec { validateBasicHeaders(response.headers()) } - // TODO (alzimmer): Figure out why getProperties returns null after setHTTPHeaders - /*def "Set HTTP headers min"() { + def "Set HTTP headers min"() { when: BlobProperties properties = bu.getProperties().value() BlobHTTPHeaders headers = new BlobHTTPHeaders() @@ -369,15 +368,15 @@ class BlobAPITest extends APISpec { .blobContentType("type") .blobCacheControl(properties.cacheControl()) .blobContentLanguage(properties.contentLanguage()) - .blobContentMD5(Base64.getDecoder().decode(properties.contentMD5())) + .blobContentMD5(Base64.getEncoder().encode(MessageDigest.getInstance("MD5").digest(defaultData.array()))) bu.setHTTPHeaders(headers) then: - bu.getProperties().headers().value("x-ms-blob-content-type") == "type" - }*/ + bu.getProperties().headers().value("Content-Type") == "type" + } - /*@Unroll + @Unroll def "Set HTTP headers headers"() { setup: BlobHTTPHeaders putHeaders = new BlobHTTPHeaders().blobCacheControl(cacheControl) @@ -398,7 +397,7 @@ class BlobAPITest extends APISpec { cacheControl | contentDisposition | contentEncoding | contentLanguage | contentMD5 | contentType null | null | null | null | null | null "control" | "disposition" | "encoding" | "language" | Base64.getEncoder().encode(MessageDigest.getInstance("MD5").digest(defaultData.array())) | "type" - }*/ + } @Unroll diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/BlockBlobAPITest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/BlockBlobAPITest.groovy index 63ec8837697b1..d5aaa8a65dcd5 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/BlockBlobAPITest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/BlockBlobAPITest.groovy @@ -349,13 +349,16 @@ class BlockBlobAPITest extends APISpec { bu.commitBlockList(ids, headers, null, null, null) Response response = bu.getProperties() + // If the value isn't set the service will automatically set it + contentType = (contentType == null) ? "application/octet-stream" : contentType + then: validateBlobProperties(response, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentMD5, contentType) where: cacheControl | contentDisposition | contentEncoding | contentLanguage | contentMD5 | contentType null | null | null | null | null | null - // "control" | "disposition" | "encoding" | "language" | MessageDigest.getInstance("MD5").digest(defaultData.array()) | "type" TODO (alzimmer): Figure out why getProperties returns null for this one + "control" | "disposition" | "encoding" | "language" | MessageDigest.getInstance("MD5").digest(defaultData.array()) | "type" } @Unroll @@ -614,8 +617,7 @@ class BlockBlobAPITest extends APISpec { bu.upload(null, 0).statusCode() == 201 } - // TODO (alzimmer): Determine why getProperties returns null - /*@Unroll + @Unroll def "Upload headers"() { setup: BlobHTTPHeaders headers = new BlobHTTPHeaders().blobCacheControl(cacheControl) @@ -629,14 +631,18 @@ class BlockBlobAPITest extends APISpec { bu.upload(defaultInputStream.get(), defaultDataSize, headers, null, null, null) Response response = bu.getProperties() + // If the value isn't set the service will automatically set it + contentMD5 = (contentMD5 == null) ? MessageDigest.getInstance("MD5").digest(defaultData.array()) : contentMD5 + contentType = (contentType == null) ? "application/octet-stream" : contentType + then: validateBlobProperties(response, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentMD5, contentType) where: - cacheControl | contentDisposition | contentEncoding | contentLanguage | contentMD5 | contentType - null | null | null | null | null | null - "control" | "disposition" | "encoding" | "language" | MessageDigest.getInstance("MD5").digest(defaultData.array()) | "type" - }*/ + cacheControl | contentDisposition | contentEncoding | contentLanguage | contentMD5 | contentType + null | null | null | null | null | null + "control" | "disposition" | "encoding" | "language" | MessageDigest.getInstance("MD5").digest(defaultData.array()) | "type" + } @Unroll def "Upload metadata"() { diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/PageBlobAPITest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/PageBlobAPITest.groovy index 333fee7936084..d0d26275228fa 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/PageBlobAPITest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/PageBlobAPITest.groovy @@ -61,6 +61,9 @@ class PageBlobAPITest extends APISpec { Response response = bu.getProperties(null, null) + // If the value isn't set the service will automatically set it + contentType = (contentType == null) ? "application/octet-stream" : contentType + then: validateBlobProperties(response, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentMD5, contentType) From c88a4c3ba1523f39492b9c65bfcfe5373968c32a Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Thu, 11 Jul 2019 15:06:00 -0700 Subject: [PATCH 04/19] Partial work for implementing and testing reliable download options --- .../azure/storage/blob/DownloadResponse.java | 156 +++++++++++++++++- .../com/azure/storage/blob/APISpec.groovy | 88 ++++++---- .../com/azure/storage/blob/BlobAPITest.groovy | 93 ++++++----- 3 files changed, 259 insertions(+), 78 deletions(-) diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadResponse.java b/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadResponse.java index 7acc6427dcf87..65fc6eb69c086 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadResponse.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadResponse.java @@ -1,13 +1,17 @@ +/* // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.azure.storage.blob; +import com.azure.storage.blob.models.BlobDownloadHeaders; import com.azure.storage.blob.models.ReliableDownloadOptions; import io.netty.buffer.ByteBuf; import java.io.IOException; import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Map; public class DownloadResponse { private final DownloadAsyncResponse asyncResponse; @@ -16,13 +20,15 @@ public class DownloadResponse { this.asyncResponse = asyncResponse; } - /** + */ +/** * Gets the body of the download response. * * @param outputStream Stream that has the response body read into it * @param options Options for the download * @throws IOException If an I/O error occurs - */ + *//* + public void body(OutputStream outputStream, ReliableDownloadOptions options) throws IOException { for (ByteBuf buffer : this.asyncResponse.body(options).toIterable()) { buffer.readBytes(outputStream, buffer.readableBytes()); @@ -31,7 +37,8 @@ public void body(OutputStream outputStream, ReliableDownloadOptions options) thr } //TODO (unknown): determine signature(s) to use - /*public InputStream body(ReliableDownloadOptions options) { + */ +/*public InputStream body(ReliableDownloadOptions options) { return new InputStream() { DownloadAsyncResponse response = asyncResponse; @@ -40,5 +47,146 @@ public int read() throws IOException { return 0; } }; - }*/ + }*//* + +} + +*/ + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob; + +import com.azure.core.http.HttpPipeline; +import com.azure.core.http.rest.ResponseBase; +import com.azure.storage.blob.models.BlobAccessConditions; +import com.azure.storage.blob.models.BlobDownloadHeaders; +import com.azure.storage.blob.models.BlobRange; +import com.azure.storage.blob.models.ReliableDownloadOptions; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.time.Duration; +import java.util.Map; +import java.util.function.Function; + +/** + * {@code DownloadResponse} wraps the protocol-layer response from {@link BlobClient#download(OutputStream, + * ReliableDownloadOptions, BlobRange, BlobAccessConditions, boolean, Duration)} to automatically retry failed reads + * from the body as appropriate. If the download is interrupted, the {@code DownloadResponse} will make a request to + * resume the download from where it left off, allowing the user to consume the data as one continuous stream, for any + * interruptions are hidden. The retry behavior is defined by the options passed to the {@link #body(ReliableDownloadOptions)}. + * The download will also lock on the blob's etag to ensure consistency. + *

+ * Note that the retries performed as a part of this reader are composed with those of any retries in an {@link + * HttpPipeline} used in conjunction with this reader. That is, if this object issues a request to resume a download, + * an underlying pipeline may issue several retries as a part of that request. Furthermore, this reader only retries on + * network errors; timeouts and unexpected status codes are not retried. Therefore, the behavior of this reader is + * entirely independent of and in no way coupled to an {@link HttpPipeline}'s retry mechanism. + */ +public final class DownloadResponse { + private final HTTPGetterInfo info; + + private final ResponseBase> rawResponse; + + private final Function> getter; + + + // The constructor is package-private because customers should not be creating their own responses. + public DownloadResponse(ResponseBase> response, HTTPGetterInfo info, + Function> getter) { + Utility.assertNotNull("getter", getter); + Utility.assertNotNull("info", info); + Utility.assertNotNull("info.eTag", info.eTag()); + this.rawResponse = response; + this.info = info; + this.getter = getter; + } + + /** + * Returns the response body which has been modified to enable reliably reading data if desired (if + * {@code options.maxRetryRequests > 0}. If retries are enabled, if a connection fails while reading, the stream + * will make additional requests to reestablish a connection and continue reading. + * + * @param options + * {@link ReliableDownloadOptions} + * + * @return A {@code Flowable} which emits the data as {@code ByteBuffer}s. + */ + public Flux body(ReliableDownloadOptions options) { + ReliableDownloadOptions optionsReal = options == null ? new ReliableDownloadOptions() : options; + if (optionsReal.maxRetryRequests() == 0) { + return this.rawResponse.value(); + } + + /* + We pass -1 for currentRetryCount because we want tryContinueFlux to receive a value of 0 for number of + retries as we have not actually retried yet, only made the initial try. Because applyReliableDownload() will + add 1 before calling into tryContinueFlux, we set the initial value to -1. + */ + return this.applyReliableDownload(this.rawResponse.value(), -1, optionsReal); + } + + private Flux tryContinueFlux(Throwable t, int retryCount, ReliableDownloadOptions options) { + // If all the errors are exhausted, return this error to the user. + if (retryCount > options.maxRetryRequests() || !(t instanceof IOException)) { + return Flux.error(t); + } else { + /* + We wrap this in a try catch because we don't know the behavior of the getter. Most errors would probably + come from an unsuccessful request, which would be propagated through the onError methods. However, it is + possible the method call that returns a Single is what throws (like how our apis throw some exceptions at + call time rather than at subscription time. + */ + try { + // Get a new response and try reading from it. + return getter.apply(this.info) + .flatMapMany(response -> + /* + Do not compound the number of retries by passing in another set of downloadOptions; just get + the raw body. + */ + this.applyReliableDownload(this.rawResponse.value(), retryCount, options)); + } catch (Exception e) { + // If the getter fails, return the getter failure to the user. + return Flux.error(e); + } + } + } + + private Flux applyReliableDownload(Flux data, int currentRetryCount, ReliableDownloadOptions options) { + return data.doOnNext(buffer -> { + /* + Update how much data we have received in case we need to retry and propagate to the user the data we + have received. + */ + this.info.offset(this.info.offset() + buffer.remaining()); + if (this.info.count() != null) { + this.info.count(this.info.count() - buffer.remaining()); + } + }).onErrorResume(t2 -> { + // Increment the retry count and try again with the new exception. + return tryContinueFlux(t2, currentRetryCount + 1, options); + }); + } + + public int statusCode() { + return this.rawResponse.statusCode(); + } + + public BlobDownloadHeaders headers() { + return this.rawResponse.deserializedHeaders(); + } + + public Map rawHeaders() { + return this.rawResponse.headers().toMap(); + } + + public ResponseBase> rawResponse() { + return this.rawResponse; + } } diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy index 8eb35ff82eb8d..a387bbb8835a8 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy @@ -12,6 +12,7 @@ import com.azure.identity.credential.EnvironmentCredential import com.azure.storage.blob.BlobProperties import com.azure.storage.blob.models.* import com.azure.storage.common.credentials.SharedKeyCredential +import io.netty.buffer.ByteBuf import org.junit.Assume import org.spockframework.lang.ISpecificationContext import reactor.core.publisher.Flux @@ -490,48 +491,75 @@ class APISpec extends Specification { }.request(request) } + static class MockDownloadWithRetryPolicy implements HttpPipelinePolicy { + MockDownloadWithRetryPolicy() { + } + + @Override + Mono process(HttpPipelineCallContext httpPipelineCallContext, HttpPipelineNextPolicy httpPipelineNextPolicy) { + return httpPipelineNextPolicy.process() + .flatMap { + if (it.request().headers().value("x-ms-range") != "bytes=2-6") { + return Mono.error(new IllegalArgumentException("The range header was not set correctly on retry.")) + } else { + // ETag can be a dummy value. It's not validated, but DownloadResponse requires one + return Mono.just(new MockDownloadHttpResponse(it, 206, Flux.error(new IOException()))) + } + } + } + } + /* This is for stubbing responses that will actually go through the pipeline and autorest code. Autorest does not seem to play too nicely with mocked objects and the complex reflection stuff on both ends made it more difficult to work with than was worth it. Because this type is just for BlobDownload, we don't need to accept a header type. */ - def getStubResponseForBlobDownload(int code, Flux body, String etag) { - return new HttpResponse() { + static class MockDownloadHttpResponse extends HttpResponse { + private final int statusCode + private final HttpHeaders headers + private final Flux body - @Override - int statusCode() { - return code - } - @Override - String headerValue(String s) { - return null - } + MockDownloadHttpResponse(HttpResponse response, int code, Flux body) { + this.request(response.request()) + this.statusCode = code + this.headers = response.headers() + this.body = body + } - @Override - HttpHeaders headers() { - return new HttpHeaders() - } + @Override + int statusCode() { + return statusCode + } - @Override - Flux body() { - return body - } + @Override + String headerValue(String s) { + return null + } - @Override - Mono bodyAsByteArray() { - return null - } + @Override + HttpHeaders headers() { + return headers + } - @Override - Mono bodyAsString() { - return null - } + @Override + Flux body() { + return body + } - @Override - Mono bodyAsString(Charset charset) { - return null - } + @Override + Mono bodyAsByteArray() { + return Mono.just(new byte[0]) + } + + @Override + Mono bodyAsString() { + return Mono.just("") + } + + @Override + Mono bodyAsString(Charset charset) { + return Mono.just("") } } diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/BlobAPITest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/BlobAPITest.groovy index c284a321b84d2..53dc95830d526 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/BlobAPITest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/BlobAPITest.groovy @@ -4,11 +4,16 @@ package com.azure.storage.blob import com.azure.core.http.HttpHeaders +import com.azure.core.http.HttpPipelineCallContext +import com.azure.core.http.HttpPipelineNextPolicy +import com.azure.core.http.policy.HttpPipelinePolicy import com.azure.core.http.rest.Response import com.azure.core.http.rest.VoidResponse import com.azure.core.implementation.util.ImplUtils import com.azure.storage.blob.BlobProperties import com.azure.storage.blob.models.* +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono import spock.lang.Unroll import java.nio.ByteBuffer @@ -77,50 +82,50 @@ class BlobAPITest extends APISpec { This is to test the appropriate integration of DownloadResponse, including setting the correct range values on HTTPGetterInfo. */ -// def "Download with retry range"() { -// /* -// We are going to make a request for some range on a blob. The Flux returned will throw an exception, forcing -// a retry per the ReliableDownloadOptions. The next request should have the same range header, which was generated -// from the count and offset values in HTTPGetterInfo that was constructed on the initial call to download. We -// don't need to check the data here, but we want to ensure that the correct range is set each time. This will -// test the correction of a bug that was found which caused HTTPGetterInfo to have an incorrect offset when it was -// constructed in BlobClient.download(). -// */ -// setup: -// HttpPipelinePolicy mockPolicy = Mock(HttpPipelinePolicy) { -// process(_ as HttpPipelineCallContext, _ as HttpPipelineNextPolicy) >> { -// HttpPipelineCallContext context, HttpPipelineNextPolicy next -> -// HttpRequest request = context.httpRequest() -// if (request.headers().value("x-ms-range") != "bytes=2-6") { -// return Mono.error(new IllegalArgumentException("The range header was not set correctly on retry.")) -// } -// else { -// // ETag can be a dummy value. It's not validated, but DownloadResponse requires one -// // TODO stub responses failing azure.core.implementation checks; too many nulls -// return Mono.just(getStubResponseForBlobDownload(206, Flux.error(new IOException()), "etag")) -// } -// } -// } -// -// BlobClient bu2 = new BlobClientBuilder() -// .endpoint(bu.getBlobUrl().toString()) -// .credential(primaryCreds) -// .addPolicy(mockPolicy) -// .buildClient() -// -// when: -// def range = new BlobRange(2, 5L) -// def options = new ReliableDownloadOptions().maxRetryRequests(3) -// bu2.download(new ByteArrayOutputStream(), options, range, null, false, null) -// -// then: -// /* -// Because the dummy Flux always throws an error. This will also validate that an IllegalArgumentException is -// NOT thrown because the types would not match. -// */ -// def e = thrown(RuntimeException) -// e.getCause() instanceof IOException -// } + def "Download with retry range"() { + /* + We are going to make a request for some range on a blob. The Flux returned will throw an exception, forcing + a retry per the ReliableDownloadOptions. The next request should have the same range header, which was generated + from the count and offset values in HTTPGetterInfo that was constructed on the initial call to download. We + don't need to check the data here, but we want to ensure that the correct range is set each time. This will + test the correction of a bug that was found which caused HTTPGetterInfo to have an incorrect offset when it was + constructed in BlobClient.download(). + */ + setup: + HttpPipelinePolicy mockPolicy = Mock(HttpPipelinePolicy) { + process(_ as HttpPipelineCallContext, _ as HttpPipelineNextPolicy) >> { + HttpPipelineCallContext context, HttpPipelineNextPolicy next -> + return next.process() + .flatMap { + if (it.request().headers().value("x-ms-range") != "bytes=2-6") { + return Mono.error(new IllegalArgumentException("The range header was not set correctly on retry.")) + } else { + // ETag can be a dummy value. It's not validated, but DownloadResponse requires one + return Mono.just(new MockDownloadHttpResponse(it, 206, Flux.error(new IOException()))) + } + } + } + } + + BlobClient bu2 = new BlobClientBuilder() + .endpoint(bu.getBlobUrl().toString()) + .credential(primaryCreds) + .addPolicy(mockPolicy) + .buildClient() + + when: + BlobRange range = new BlobRange(2, 5L) + ReliableDownloadOptions options = new ReliableDownloadOptions().maxRetryRequests(3) + bu2.download(new ByteArrayOutputStream(), options, range, null, false, null) + + then: + /* + Because the dummy Flux always throws an error. This will also validate that an IllegalArgumentException is + NOT thrown because the types would not match. + */ + def e = thrown(RuntimeException) + e.getCause() instanceof IOException + } def "Download min"() { when: From 1ec9438d210a125ec9b265c121a9b70c217e5922 Mon Sep 17 00:00:00 2001 From: Gauri Prasad <51212198+gapra-msft@users.noreply.github.com> Date: Fri, 12 Jul 2019 11:57:48 -0700 Subject: [PATCH 05/19] Storage SAS implementation (#4404) * SAS implementation * Fixed some minor formatting issues * Fixed checkstyle problems and test issue --- .../azure/storage/blob/BlobAsyncClient.java | 259 ++++ .../com/azure/storage/blob/BlobClient.java | 207 ++++ .../com/azure/storage/blob/Constants.java | 15 + .../storage/blob/ContainerAsyncClient.java | 231 ++++ .../azure/storage/blob/ContainerClient.java | 188 +++ .../storage/blob/SASQueryParameters.java | 4 +- .../blob/ServiceSASSignatureValues.java | 182 +-- .../storage/blob/StorageAsyncClient.java | 70 ++ .../com/azure/storage/blob/StorageClient.java | 49 + .../java/com/azure/storage/blob/Utility.java | 23 + .../credentials/SASTokenCredential.java | 96 +- .../policy/SharedKeyCredentialPolicy.java | 9 + .../com/azure/storage/blob/SASTest.groovy | 1079 +++++++++++++++++ 13 files changed, 2315 insertions(+), 97 deletions(-) create mode 100644 storage/client/blob/src/test/java/com/azure/storage/blob/SASTest.groovy diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java index 7ec72ecf30b53..5042f9e782af5 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java @@ -21,6 +21,8 @@ import com.azure.storage.blob.models.ModifiedAccessConditions; import com.azure.storage.blob.models.ReliableDownloadOptions; import com.azure.storage.blob.models.StorageAccountInfo; +import com.azure.storage.blob.models.UserDelegationKey; +import com.azure.storage.common.credentials.SharedKeyCredential; import io.netty.buffer.ByteBuf; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -35,6 +37,7 @@ import java.nio.channels.AsynchronousFileChannel; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; @@ -774,4 +777,260 @@ public Mono> getAccountInfo() { .getAccountInfo() .map(rb -> new SimpleResponse<>(rb, new StorageAccountInfo(rb.deserializedHeaders()))); } + + /** + * Generates a user delegation SAS with the specified parameters + * + * @param userDelegationKey + * The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName + * The {@code String} account name for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, + BlobSASPermission permissions, OffsetDateTime expiryTime) { + return this.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, null /* + startTime */, null /* version */, null /*sasProtocol */, null /* ipRange */, null /* cacheControl */, null + /*contentDisposition */, null /* contentEncoding */, null /* contentLanguage */, null /* contentType */); + } + + /** + * Generates a user delegation SAS token with the specified parameters + * + * @param userDelegationKey + * The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName + * The {@code String} account name for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, + BlobSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange) { + return this.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, startTime, + version, sasProtocol, ipRange, null /* cacheControl */, null /* contentDisposition */, null /* + contentEncoding */, null /* contentLanguage */, null /* contentType */); + } + + /** + * Generates a user delegation SAS token with the specified parameters + * + * @param userDelegationKey + * The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName + * The {@code String} account name for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * @param cacheControl + * An optional {@code String} cache-control header for the SAS. + * @param contentDisposition + * An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding + * An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage + * An optional {@code String} content-language header for the SAS. + * @param contentType + * An optional {@code String} content-type header for the SAS. + * + * @return + * A string that represents the SAS token + */ + public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, + BlobSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange, String cacheControl, String contentDisposition, + String contentEncoding, String contentLanguage, String contentType) { + + ServiceSASSignatureValues serviceSASSignatureValues = new ServiceSASSignatureValues(version, sasProtocol, + startTime, expiryTime, permissions == null ? null : permissions.toString(), ipRange, null /* identifier*/, + cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); + + ServiceSASSignatureValues values = configureServiceSASSignatureValues(serviceSASSignatureValues, accountName); + + SASQueryParameters sasQueryParameters = values.generateSASQueryParameters(userDelegationKey); + + return sasQueryParameters.encode(); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(BlobSASPermission permissions, OffsetDateTime expiryTime) { + return this.generateSAS(null, permissions, expiryTime, null /* startTime */, /* identifier */ null /* + version */, null /* sasProtocol */, null /* ipRange */, null /* cacheControl */, null /* contentLanguage*/, + null /* contentEncoding */, null /* contentLanguage */, null /* contentType */); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param identifier + * The {@code String} name of the access policy on the container this SAS references if any + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(String identifier) { + return this.generateSAS(identifier, null /* permissions */, null /* expiryTime */, null /* startTime */, + null /* version */, null /* sasProtocol */, null /* ipRange */, null /* cacheControl */, null /* + contentLanguage*/, null /* contentEncoding */, null /* contentLanguage */, null /* contentType */); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param identifier + * The {@code String} name of the access policy on the container this SAS references if any + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(String identifier, BlobSASPermission permissions, OffsetDateTime expiryTime, + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange) { + return this.generateSAS(identifier, permissions, expiryTime, startTime, version, sasProtocol, ipRange, null + /* cacheControl */, null /* contentLanguage*/, null /* contentEncoding */, null /* contentLanguage */, + null /* contentType */); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param identifier + * The {@code String} name of the access policy on the container this SAS references if any + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * @param cacheControl + * An optional {@code String} cache-control header for the SAS. + * @param contentDisposition + * An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding + * An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage + * An optional {@code String} content-language header for the SAS. + * @param contentType + * An optional {@code String} content-type header for the SAS. + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(String identifier, BlobSASPermission permissions, OffsetDateTime expiryTime, + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange, String cacheControl, + String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { + + ServiceSASSignatureValues serviceSASSignatureValues = new ServiceSASSignatureValues(version, sasProtocol, + startTime, expiryTime, permissions == null ? null : permissions.toString(), ipRange, identifier, + cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); + + SharedKeyCredential sharedKeyCredential = + Utility.getSharedKeyCredential(this.blobAsyncRawClient.azureBlobStorage.httpPipeline()); + + Utility.assertNotNull("sharedKeyCredential", sharedKeyCredential); + + ServiceSASSignatureValues values = configureServiceSASSignatureValues(serviceSASSignatureValues, + sharedKeyCredential.accountName()); + + SASQueryParameters sasQueryParameters = values.generateSASQueryParameters(sharedKeyCredential); + + return sasQueryParameters.encode(); + } + + /** + * Sets serviceSASSignatureValues parameters dependent on the current blob type + */ + ServiceSASSignatureValues configureServiceSASSignatureValues(ServiceSASSignatureValues serviceSASSignatureValues, + String accountName) { + + // Set canonical name + serviceSASSignatureValues.canonicalName(this.blobAsyncRawClient.azureBlobStorage.url(), accountName); + + // Set snapshotId + serviceSASSignatureValues.snapshotId(getSnapshotId()); + + // Set resource + if (isSnapshot()) { + serviceSASSignatureValues.resource(Constants.UrlConstants.SAS_BLOB_SNAPSHOT_CONSTANT); + } else { + serviceSASSignatureValues.resource(Constants.UrlConstants.SAS_BLOB_CONSTANT); + } + + return serviceSASSignatureValues; + } + + /** + * Gets the snapshotId for a blob resource + * + * @return + * A string that represents the snapshotId of the snapshot blob + */ + public String getSnapshotId() { + return this.blobAsyncRawClient.snapshot; + } + + /** + * Determines if a blob is a snapshot + * + * @return + * A boolean that indicates if a blob is a snapshot + */ + public boolean isSnapshot() { + return this.blobAsyncRawClient.snapshot != null; + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClient.java index f47c3cf6d7830..21a2212713f5c 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClient.java @@ -16,6 +16,7 @@ import com.azure.storage.blob.models.ModifiedAccessConditions; import com.azure.storage.blob.models.ReliableDownloadOptions; import com.azure.storage.blob.models.StorageAccountInfo; +import com.azure.storage.blob.models.UserDelegationKey; import reactor.core.publisher.Mono; import java.io.IOException; @@ -23,6 +24,7 @@ import java.io.UncheckedIOException; import java.net.URL; import java.time.Duration; +import java.time.OffsetDateTime; /** * Client to a blob of any type: block, append, or page. It may only be instantiated through a {@link BlobClientBuilder} or via @@ -800,4 +802,209 @@ public Response getAccountInfo(Duration timeout) { return Utility.blockWithOptionalTimeout(response, timeout); } + + /** + * Generates a user delegation SAS token with the specified parameters + * + * @param userDelegationKey + * The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName + * The {@code String} account name for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, + BlobSASPermission permissions, OffsetDateTime expiryTime) { + return this.blobAsyncClient.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime); + } + + /** + * Generates a user delegation SAS token with the specified parameters + * + * @param userDelegationKey + * The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName + * The {@code String} account name for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, + BlobSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange) { + return this.blobAsyncClient.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, + startTime, version, sasProtocol, ipRange); + } + + /** + * Generates a user delegation SAS token with the specified parameters + * + * @param userDelegationKey + * The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName + * The {@code String} account name for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * @param cacheControl + * An optional {@code String} cache-control header for the SAS. + * @param contentDisposition + * An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding + * An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage + * An optional {@code String} content-language header for the SAS. + * @param contentType + * An optional {@code String} content-type header for the SAS. + * + * @return + * A string that represents the SAS token + */ + public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, + BlobSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange, String cacheControl, String contentDisposition, + String contentEncoding, String contentLanguage, String contentType) { + return this.blobAsyncClient.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, + startTime, version, sasProtocol, ipRange, cacheControl, contentDisposition, contentEncoding, + contentLanguage, contentType); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(OffsetDateTime expiryTime, BlobSASPermission permissions) { + return this.blobAsyncClient.generateSAS(permissions, expiryTime); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param identifier + * The {@code String} name of the access policy on the container this SAS references if any + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(String identifier) { + return this.blobAsyncClient.generateSAS(identifier); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param identifier + * The {@code String} name of the access policy on the container this SAS references if any + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(String identifier, BlobSASPermission permissions, OffsetDateTime expiryTime, + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange) { + return this.blobAsyncClient.generateSAS(identifier, permissions, expiryTime, startTime, version, sasProtocol, + ipRange); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param identifier + * The {@code String} name of the access policy on the container this SAS references if any + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * @param cacheControl + * An optional {@code String} cache-control header for the SAS. + * @param contentDisposition + * An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding + * An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage + * An optional {@code String} content-language header for the SAS. + * @param contentType + * An optional {@code String} content-type header for the SAS. + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(String identifier, BlobSASPermission permissions, OffsetDateTime expiryTime, + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange, String cacheControl, + String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { + return this.blobAsyncClient.generateSAS(identifier, permissions, expiryTime, startTime, version, sasProtocol, + ipRange, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); + } + + /** + * Gets the snapshotId for a blob resource + * + * @return + * A string that represents the snapshotId of the snapshot blob + */ + public String getSnapshotId() { + return this.blobAsyncClient.getSnapshotId(); + } + + /** + * Determines if a blob is a snapshot + * + * @return + * A boolean that indicates if a blob is a snapshot + */ + public boolean isSnapshot() { + return this.blobAsyncClient.isSnapshot(); + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/Constants.java b/storage/client/blob/src/main/java/com/azure/storage/blob/Constants.java index 653625b572c15..fa812df4b531b 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/Constants.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/Constants.java @@ -288,6 +288,21 @@ static final class UrlConstants { */ public static final String SAS_SIGNED_KEY_VERSION = "skv"; + /** + * The SAS blob constant. + */ + public static final String SAS_BLOB_CONSTANT = "b"; + + /** + * The SAS blob snapshot constant. + */ + public static final String SAS_BLOB_SNAPSHOT_CONSTANT = "bs"; + + /** + * The SAS blob snapshot constant. + */ + public static final String SAS_CONTAINER_CONSTANT = "c"; + private UrlConstants() { // Private to prevent construction. } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncClient.java index 406a7440b0c5b..f979f084ac151 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncClient.java @@ -24,12 +24,15 @@ import com.azure.storage.blob.models.PublicAccessType; import com.azure.storage.blob.models.SignedIdentifier; import com.azure.storage.blob.models.StorageAccountInfo; +import com.azure.storage.blob.models.UserDelegationKey; +import com.azure.storage.common.credentials.SharedKeyCredential; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; +import java.time.OffsetDateTime; import java.util.List; /** @@ -878,4 +881,232 @@ public Mono> getAccountInfo() { .getAccountInfo() .map(rb -> new SimpleResponse<>(rb, new StorageAccountInfo(rb.deserializedHeaders()))); } + + /** + * Generates a user delegation SAS with the specified parameters + * + * @param userDelegationKey + * The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName + * The {@code String} account name for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, + ContainerSASPermission permissions, OffsetDateTime expiryTime) { + return this.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, null /* + startTime */, null /* version */, null /* sasProtocol */, null /* ipRange */, null /* cacheControl */, null + /* contentDisposition */, null /* contentEncoding */, null /* contentLanguage */, null /* contentType */); + } + + /** + * Generates a user delegation SAS token with the specified parameters + * + * @param userDelegationKey + * The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName + * The {@code String} account name for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, + ContainerSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange) { + return this.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, startTime, + version, sasProtocol, ipRange, null /* cacheControl */, null /* contentDisposition */, null /* + contentEncoding */, null /* contentLanguage */, null /* contentType */); + } + + /** + * Generates a user delegation SAS token with the specified parameters + * + * @param userDelegationKey + * The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName + * The {@code String} account name for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * @param cacheControl + * An optional {@code String} cache-control header for the SAS. + * @param contentDisposition + * An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding + * An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage + * An optional {@code String} content-language header for the SAS. + * @param contentType + * An optional {@code String} content-type header for the SAS. + * + * @return + * A string that represents the SAS token + */ + public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, + ContainerSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange, String cacheControl, String contentDisposition, + String contentEncoding, String contentLanguage, String contentType) { + ServiceSASSignatureValues serviceSASSignatureValues = new ServiceSASSignatureValues(version, sasProtocol, + startTime, expiryTime, permissions == null ? null : permissions.toString(), ipRange, null /* identifier*/, + cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); + + ServiceSASSignatureValues values = configureServiceSASSignatureValues(serviceSASSignatureValues, accountName); + + SASQueryParameters sasQueryParameters = values.generateSASQueryParameters(userDelegationKey); + + return sasQueryParameters.encode(); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(ContainerSASPermission permissions, OffsetDateTime expiryTime) { + return this.generateSAS(null, permissions, /* identifier */ expiryTime, null /* startTime */, null /* version + */, null /* sasProtocol */, null /* ipRange */, null /* cacheControl */, null /* contentDisposition */, + null /* contentEncoding */, null /* contentLanguage */, null /*contentType*/); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param identifier + * The {@code String} name of the access policy on the container this SAS references if any + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(String identifier) { + return this.generateSAS(identifier, null /* permissions*/, null /* expiryTime */, null /* startTime */, null + /* version */, null /* sasProtocol */, null /* ipRange */, null /* cacheControl */, null /* + contentDisposition */, null /* contentEncoding */, null /* contentLanguage */, null /*contentType*/); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param identifier + * The {@code String} name of the access policy on the container this SAS references if any + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(String identifier, ContainerSASPermission permissions, OffsetDateTime expiryTime, + OffsetDateTime startTime, + String version, SASProtocol sasProtocol, IPRange ipRange) { + return this.generateSAS(identifier, permissions, expiryTime, startTime, version, sasProtocol, ipRange, null + /* cacheControl */, null /* contentDisposition */, null /* contentEncoding */, null /* contentLanguage */, + null /*contentType*/); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param identifier + * The {@code String} name of the access policy on the container this SAS references if any + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * @param cacheControl + * An optional {@code String} cache-control header for the SAS. + * @param contentDisposition + * An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding + * An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage + * An optional {@code String} content-language header for the SAS. + * @param contentType + * An optional {@code String} content-type header for the SAS. + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(String identifier, ContainerSASPermission permissions, OffsetDateTime expiryTime, + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange, String cacheControl, + String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { + ServiceSASSignatureValues serviceSASSignatureValues = new ServiceSASSignatureValues(version, sasProtocol, + startTime, expiryTime, permissions == null ? null : permissions.toString(), ipRange, identifier, + cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); + + SharedKeyCredential sharedKeyCredential = + Utility.getSharedKeyCredential(this.containerAsyncRawClient.azureBlobStorage.httpPipeline()); + + Utility.assertNotNull("sharedKeyCredential", sharedKeyCredential); + + ServiceSASSignatureValues values = configureServiceSASSignatureValues(serviceSASSignatureValues, + sharedKeyCredential.accountName()); + + SASQueryParameters sasQueryParameters = values.generateSASQueryParameters(sharedKeyCredential); + + return sasQueryParameters.encode(); + } + + /** + * Sets serviceSASSignatureValues parameters dependent on the current blob type + */ + private ServiceSASSignatureValues configureServiceSASSignatureValues(ServiceSASSignatureValues serviceSASSignatureValues, String accountName) { + // Set canonical name + serviceSASSignatureValues.canonicalName(this.containerAsyncRawClient.azureBlobStorage.url(), accountName); + + // Set snapshotId to null + serviceSASSignatureValues.snapshotId(null); + + // Set resource + serviceSASSignatureValues.resource(Constants.UrlConstants.SAS_CONTAINER_CONSTANT); + return serviceSASSignatureValues; + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClient.java index b8c1f9d8bd09e..c9733fd3a7f61 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClient.java @@ -15,11 +15,13 @@ import com.azure.storage.blob.models.PublicAccessType; import com.azure.storage.blob.models.SignedIdentifier; import com.azure.storage.blob.models.StorageAccountInfo; +import com.azure.storage.blob.models.UserDelegationKey; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.net.URL; import java.time.Duration; +import java.time.OffsetDateTime; import java.util.List; /** @@ -738,4 +740,190 @@ public Response getAccountInfo(Duration timeout) { return Utility.blockWithOptionalTimeout(response, timeout); } + + /** + * Generates a user delegation SAS token with the specified parameters + * + * @param userDelegationKey + * The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName + * The {@code String} account name for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, + ContainerSASPermission permissions, OffsetDateTime expiryTime) { + return this.containerAsyncClient.generateUserDelegationSAS(userDelegationKey, accountName, permissions, + expiryTime); + } + + /** + * Generates a user delegation SAS token with the specified parameters + * + * @param userDelegationKey + * The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName + * The {@code String} account name for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, + ContainerSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange) { + return this.containerAsyncClient.generateUserDelegationSAS(userDelegationKey, accountName, permissions, + expiryTime, startTime, version, sasProtocol, ipRange); + } + + /** + * Generates a user delegation SAS token with the specified parameters + * + * @param userDelegationKey + * The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName + * The {@code String} account name for the SAS + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * @param cacheControl + * An optional {@code String} cache-control header for the SAS. + * @param contentDisposition + * An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding + * An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage + * An optional {@code String} content-language header for the SAS. + * @param contentType + * An optional {@code String} content-type header for the SAS. + * + * @return + * A string that represents the SAS token + */ + public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, + ContainerSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange, String cacheControl, String contentDisposition, + String contentEncoding, String contentLanguage, String contentType) { + return this.containerAsyncClient.generateUserDelegationSAS(userDelegationKey, accountName, permissions, + expiryTime, startTime, version, sasProtocol, ipRange, cacheControl, contentDisposition, contentEncoding, + contentLanguage, contentType); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(ContainerSASPermission permissions, OffsetDateTime expiryTime) { + return this.containerAsyncClient.generateSAS(permissions, expiryTime); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param identifier + * The {@code String} name of the access policy on the container this SAS references if any + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(String identifier) { + return this.containerAsyncClient.generateSAS(identifier); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param identifier + * The {@code String} name of the access policy on the container this SAS references if any + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(String identifier, ContainerSASPermission permissions, OffsetDateTime expiryTime, + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange) { + return this.containerAsyncClient.generateSAS(identifier, permissions, expiryTime, startTime, version, + sasProtocol, ipRange); + } + + /** + * Generates a SAS token with the specified parameters + * + * @param identifier + * The {@code String} name of the access policy on the container this SAS references if any + * @param permissions + * The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the SAS + * @param startTime + * An optional {@code OffsetDateTime} start time for the SAS + * @param version + * An optional {@code String} version for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * @param cacheControl + * An optional {@code String} cache-control header for the SAS. + * @param contentDisposition + * An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding + * An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage + * An optional {@code String} content-language header for the SAS. + * @param contentType + * An optional {@code String} content-type header for the SAS. + * + * @return + * A string that represents the SAS token + */ + public String generateSAS(String identifier, ContainerSASPermission permissions, OffsetDateTime expiryTime, + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange, String cacheControl, + String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { + return this.containerAsyncClient.generateSAS(identifier, permissions, expiryTime, startTime, version, + sasProtocol, ipRange, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/SASQueryParameters.java b/storage/client/blob/src/main/java/com/azure/storage/blob/SASQueryParameters.java index b6afd82addcab..cd918e636bbd4 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/SASQueryParameters.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/SASQueryParameters.java @@ -333,9 +333,7 @@ UserDelegationKey userDelegationKey() { private void tryAppendQueryParameter(StringBuilder sb, String param, Object value) { if (value != null) { - if (sb.length() == 0) { - sb.append('?'); - } else { + if (sb.length() != 0) { sb.append('&'); } sb.append(safeURLEncode(param)).append('=').append(safeURLEncode(value.toString())); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java index 7450ae699f5a4..114c28eb71209 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java @@ -6,6 +6,8 @@ import com.azure.storage.blob.models.UserDelegationKey; import com.azure.storage.common.credentials.SharedKeyCredential; +import java.net.MalformedURLException; +import java.net.URL; import java.security.InvalidKeyException; import java.time.OffsetDateTime; @@ -40,9 +42,9 @@ final class ServiceSASSignatureValues { private IPRange ipRange; - private String containerName; + private String canonicalName; - private String blobName; + private String resource; private String snapshotId; @@ -64,6 +66,43 @@ final class ServiceSASSignatureValues { ServiceSASSignatureValues() { } + /** + * Creates an object with the specified expiry time and permissions + * @param expiryTime + * @param permissions + */ + ServiceSASSignatureValues(OffsetDateTime expiryTime, String permissions) { + this.expiryTime = expiryTime; + this.permissions = permissions; + } + + /** + * Creates an object with the specified identifier + * @param identifier + */ + ServiceSASSignatureValues(String identifier) { + this.identifier = identifier; + } + + ServiceSASSignatureValues(String version, SASProtocol sasProtocol, OffsetDateTime startTime, + OffsetDateTime expiryTime, String permission, IPRange ipRange, String identifier, String cacheControl, + String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { + if (version != null) { + this.version = version; + } + this.protocol = sasProtocol; + this.startTime = startTime; + this.expiryTime = expiryTime; + this.permissions = permission; + this.ipRange = ipRange; + this.identifier = identifier; + this.cacheControl = cacheControl; + this.contentDisposition = contentDisposition; + this.contentEncoding = contentEncoding; + this.contentLanguage = contentLanguage; + this.contentType = contentType; + } + /** * The version of the service this SAS will target. If not specified, it will default to the version targeted by the * library. @@ -159,32 +198,51 @@ public ServiceSASSignatureValues ipRange(IPRange ipRange) { } /** - * The name of the container the SAS user may access. + * The resource the SAS user may access. */ - public String containerName() { - return containerName; + public String resource() { + return resource; } /** - * The name of the container the SAS user may access. + * The resource the SAS user may access. */ - public ServiceSASSignatureValues containerName(String containerName) { - this.containerName = containerName; + public ServiceSASSignatureValues resource(String resource) { + this.resource = resource; return this; } /** - * The name of the blob the SAS user may access. + * The canonical name of the object the SAS user may access. */ - public String blobName() { - return blobName; + public String canonicalName() { + return canonicalName; } /** - * The name of the blob the SAS user may access. + * The canonical name of the object the SAS user may access. */ - public ServiceSASSignatureValues blobName(String blobName) { - this.blobName = blobName; + public ServiceSASSignatureValues canonicalName(String canonicalName) { + this.canonicalName = canonicalName; + return this; + } + + /** + * The canonical name of the object the SAS user may access. + * @throws RuntimeException If urlString is a malformed URL. + */ + public ServiceSASSignatureValues canonicalName(String urlString, String accountName) { + URL url = null; + try { + url = new URL(urlString); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + + StringBuilder canonicalName = new StringBuilder("/blob"); + canonicalName.append('/').append(accountName).append(url.getPath()); + this.canonicalName = canonicalName.toString(); + return this; } @@ -192,7 +250,7 @@ public ServiceSASSignatureValues blobName(String blobName) { * The specific snapshot the SAS user may access. */ public String snapshotId() { - return snapshotId; + return this.snapshotId; } /** @@ -309,13 +367,10 @@ public ServiceSASSignatureValues contentType(String contentType) { */ public SASQueryParameters generateSASQueryParameters(SharedKeyCredential sharedKeyCredentials) { Utility.assertNotNull("sharedKeyCredentials", sharedKeyCredentials); - assertGenerateOK(); - - String resource = getResource(); - String verifiedPermissions = getVerifiedPermissions(); + assertGenerateOK(false); // Signature is generated on the un-url-encoded values. - final String stringToSign = stringToSign(verifiedPermissions, resource, sharedKeyCredentials); + final String stringToSign = stringToSign(); String signature = null; try { @@ -336,22 +391,15 @@ public SASQueryParameters generateSASQueryParameters(SharedKeyCredential sharedK * @param delegationKey * A {@link UserDelegationKey} object used to sign the SAS values. * - * @param accountName - * Name of the account holding the resource this SAS is authorizing. - * * @return {@link SASQueryParameters} * @throws Error If the accountKey is not a valid Base64-encoded string. */ - public SASQueryParameters generateSASQueryParameters(UserDelegationKey delegationKey, String accountName) { + public SASQueryParameters generateSASQueryParameters(UserDelegationKey delegationKey) { Utility.assertNotNull("delegationKey", delegationKey); - Utility.assertNotNull("accountName", accountName); - assertGenerateOK(); - - String resource = getResource(); - String verifiedPermissions = getVerifiedPermissions(); + assertGenerateOK(true); // Signature is generated on the un-url-encoded values. - final String stringToSign = stringToSign(verifiedPermissions, resource, delegationKey, accountName); + final String stringToSign = stringToSign(delegationKey); String signature = null; try { @@ -369,70 +417,39 @@ public SASQueryParameters generateSASQueryParameters(UserDelegationKey delegatio /** * Common assertions for generateSASQueryParameters overloads. */ - private void assertGenerateOK() { + private void assertGenerateOK(boolean usingUserDelegation) { Utility.assertNotNull("version", this.version); - Utility.assertNotNull("containerName", this.containerName); - if (blobName == null && snapshotId != null) { - throw new IllegalArgumentException("Cannot set a snapshotId without a blobName."); - } - } + Utility.assertNotNull("canonicalName", this.canonicalName); - /** - * Gets the resource string for SAS tokens based on object state. - */ - private String getResource() { - String resource = "c"; - if (!Utility.isNullOrEmpty(this.blobName)) { - resource = snapshotId != null && !snapshotId.isEmpty() ? "bs" : "b"; - } - - return resource; - } - - /** - * Gets the verified permissions string for SAS tokens based on object state. - */ - private String getVerifiedPermissions() { - String verifiedPermissions = null; - // Calling parse and toString guarantees the proper ordering and throws on invalid characters. - if (Utility.isNullOrEmpty(this.blobName)) { - if (this.permissions != null) { - verifiedPermissions = ContainerSASPermission.parse(this.permissions).toString(); + // Ensure either (expiryTime and permissions) or (identifier) is set + if (this.expiryTime == null || this.permissions == null) { + // Identifier is not required if user delegation is being used + if (!usingUserDelegation) { + Utility.assertNotNull("identifier", this.identifier); } } else { - if (this.permissions != null) { - verifiedPermissions = BlobSASPermission.parse(this.permissions).toString(); - } + Utility.assertNotNull("expiryTime", this.expiryTime); + Utility.assertNotNull("permissions", this.permissions); } - return verifiedPermissions; - } - - private String getCanonicalName(String accountName) { - // Container: "/blob/account/containername" - // Blob: "/blob/account/containername/blobname" - StringBuilder canonicalName = new StringBuilder("/blob"); - canonicalName.append('/').append(accountName).append('/').append(this.containerName); - - if (!Utility.isNullOrEmpty(this.blobName)) { - canonicalName.append("/").append(this.blobName); + if (this.resource != null && this.resource.equals(Constants.UrlConstants.SAS_CONTAINER_CONSTANT)) { + if (this.snapshotId != null) { + throw new IllegalArgumentException("Cannot set a snapshotId without resource being a blob."); + } } - - return canonicalName.toString(); } - private String stringToSign(final String verifiedPermissions, final String resource, - final SharedKeyCredential sharedKeyCredentials) { + private String stringToSign() { return String.join("\n", - verifiedPermissions == null ? "" : verifiedPermissions, + this.permissions == null ? "" : this.permissions, this.startTime == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), this.expiryTime == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), - getCanonicalName(sharedKeyCredentials.accountName()), + this.canonicalName == null ? "" : this.canonicalName, this.identifier == null ? "" : this.identifier, this.ipRange == null ? (new IPRange()).toString() : this.ipRange.toString(), this.protocol == null ? "" : protocol.toString(), this.version == null ? "" : this.version, - resource == null ? "" : resource, + this.resource == null ? "" : this.resource, this.snapshotId == null ? "" : this.snapshotId, this.cacheControl == null ? "" : this.cacheControl, this.contentDisposition == null ? "" : this.contentDisposition, @@ -442,13 +459,12 @@ private String stringToSign(final String verifiedPermissions, final String resou ); } - private String stringToSign(final String verifiedPermissions, final String resource, - final UserDelegationKey key, final String accountName) { + private String stringToSign(final UserDelegationKey key) { return String.join("\n", - verifiedPermissions == null ? "" : verifiedPermissions, + this.permissions == null ? "" : this.permissions, this.startTime == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), this.expiryTime == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), - getCanonicalName(accountName), + this.canonicalName == null ? "" : this.canonicalName, key.signedOid() == null ? "" : key.signedOid(), key.signedTid() == null ? "" : key.signedTid(), key.signedStart() == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(key.signedStart()), @@ -458,7 +474,7 @@ private String stringToSign(final String verifiedPermissions, final String resou this.ipRange == null ? new IPRange().toString() : this.ipRange.toString(), this.protocol == null ? "" : this.protocol.toString(), this.version == null ? "" : this.version, - resource == null ? "" : resource, + this.resource == null ? "" : this.resource, this.snapshotId == null ? "" : this.snapshotId, this.cacheControl == null ? "" : this.cacheControl, this.contentDisposition == null ? "" : this.contentDisposition, diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java index d5b0aecc3a3f1..c6c48d7e5770d 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java @@ -18,6 +18,7 @@ import com.azure.storage.blob.models.StorageServiceProperties; import com.azure.storage.blob.models.StorageServiceStats; import com.azure.storage.blob.models.UserDelegationKey; +import com.azure.storage.common.credentials.SharedKeyCredential; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -233,4 +234,73 @@ public Mono> getAccountInfo() { .getAccountInfo() .map(rb -> new SimpleResponse<>(rb, new StorageAccountInfo(rb.deserializedHeaders()))); } + + /** + * Generates an account SAS token with the specified parameters + * + * @param accountSASService + * The {@code AccountSASService} services for the account SAS + * @param accountSASResourceType + * An optional {@code AccountSASResourceType} resources for the account SAS + * @param accountSASPermission + * The {@code AccountSASPermission} permission for the account SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the account SAS + * + * @return + * A string that represents the SAS token + */ + public String generateAccountSAS(AccountSASService accountSASService, AccountSASResourceType accountSASResourceType, + AccountSASPermission accountSASPermission, OffsetDateTime expiryTime) { + return this.generateAccountSAS(accountSASService, accountSASResourceType, accountSASPermission, expiryTime, + null /* startTime */, null /* version */, null /* ipRange */, null /* sasProtocol */); + } + + /** + * Generates an account SAS token with the specified parameters + * + * @param accountSASService + * The {@code AccountSASService} services for the account SAS + * @param accountSASResourceType + * An optional {@code AccountSASResourceType} resources for the account SAS + * @param accountSASPermission + * The {@code AccountSASPermission} permission for the account SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the account SAS + * @param startTime + * The {@code OffsetDateTime} start time for the account SAS + * @param version + * The {@code String} version for the account SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateAccountSAS(AccountSASService accountSASService, AccountSASResourceType accountSASResourceType, + AccountSASPermission accountSASPermission, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, IPRange ipRange, + SASProtocol sasProtocol) { + + AccountSASSignatureValues accountSASSignatureValues = new AccountSASSignatureValues(); + accountSASSignatureValues.services(accountSASService == null ? null : accountSASService.toString()); + accountSASSignatureValues.resourceTypes(accountSASResourceType == null ? null : accountSASResourceType.toString()); + accountSASSignatureValues.permissions(accountSASPermission == null ? null : accountSASPermission.toString()); + accountSASSignatureValues.expiryTime(expiryTime); + accountSASSignatureValues.startTime(startTime); + + if (version != null) { + accountSASSignatureValues.version(version); + } + + accountSASSignatureValues.ipRange(ipRange); + accountSASSignatureValues.protocol(sasProtocol); + + SharedKeyCredential sharedKeyCredential = Utility.getSharedKeyCredential(this.storageAsyncRawClient.azureBlobStorage.httpPipeline()); + + SASQueryParameters sasQueryParameters = accountSASSignatureValues.generateSASQueryParameters(sharedKeyCredential); + + return sasQueryParameters.encode(); + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClient.java index f18cf0640fbd9..c895cdda58bbc 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClient.java @@ -290,4 +290,53 @@ public Response getAccountInfo(Duration timeout) { return Utility.blockWithOptionalTimeout(response, timeout); } + + /** + * Generates an account SAS token with the specified parameters + * + * @param accountSASService + * The {@code AccountSASService} services for the account SAS + * @param accountSASResourceType + * An optional {@code AccountSASResourceType} resources for the account SAS + * @param accountSASPermission + * The {@code AccountSASPermission} permission for the account SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the account SAS + * + * @return + * A string that represents the SAS token + */ + public String generateAccountSAS(AccountSASService accountSASService, AccountSASResourceType accountSASResourceType, + AccountSASPermission accountSASPermission, OffsetDateTime expiryTime) { + return this.storageAsyncClient.generateAccountSAS(accountSASService, accountSASResourceType, accountSASPermission, expiryTime); + } + + /** + * Generates an account SAS token with the specified parameters + * + * @param accountSASService + * The {@code AccountSASService} services for the account SAS + * @param accountSASResourceType + * An optional {@code AccountSASResourceType} resources for the account SAS + * @param accountSASPermission + * The {@code AccountSASPermission} permission for the account SAS + * @param expiryTime + * The {@code OffsetDateTime} expiry time for the account SAS + * @param startTime + * The {@code OffsetDateTime} start time for the account SAS + * @param version + * The {@code String} version for the account SAS + * @param ipRange + * An optional {@code IPRange} ip address range for the SAS + * @param sasProtocol + * An optional {@code SASProtocol} protocol for the SAS + * + * @return + * A string that represents the SAS token + */ + public String generateAccountSAS(AccountSASService accountSASService, AccountSASResourceType accountSASResourceType, + AccountSASPermission accountSASPermission, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, IPRange ipRange, + SASProtocol sasProtocol) { + return this.storageAsyncClient.generateAccountSAS(accountSASService, accountSASResourceType, accountSASPermission, expiryTime, startTime, version, ipRange, sasProtocol); + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/Utility.java b/storage/client/blob/src/main/java/com/azure/storage/blob/Utility.java index 3b5d073c4023e..252a5e83bbaf2 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/Utility.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/Utility.java @@ -5,9 +5,13 @@ import com.azure.core.http.HttpHeader; import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpPipeline; +import com.azure.core.http.policy.HttpPipelinePolicy; import com.azure.core.implementation.http.UrlBuilder; import com.azure.storage.blob.models.StorageErrorException; import com.azure.storage.blob.models.UserDelegationKey; +import com.azure.storage.common.credentials.SharedKeyCredential; +import com.azure.storage.common.policy.SharedKeyCredentialPolicy; import reactor.core.publisher.Mono; import reactor.util.annotation.Nullable; @@ -387,4 +391,23 @@ static T blockWithOptionalTimeout(Mono response, @Nullable Duration timeo return response.block(timeout); } } + + /** + * Gets the SharedKeyCredential from the HttpPipeline + * + * @param httpPipeline + * The {@code HttpPipeline} httpPipeline from which a sharedKeyCredential will be extracted + * + * @return The {@code SharedKeyCredential} sharedKeyCredential in the httpPipeline + */ + static SharedKeyCredential getSharedKeyCredential(HttpPipeline httpPipeline) { + for (int i = 0; i < httpPipeline.getPolicyCount(); i++) { + HttpPipelinePolicy httpPipelinePolicy = httpPipeline.getPolicy(i); + if (httpPipelinePolicy instanceof SharedKeyCredentialPolicy) { + SharedKeyCredentialPolicy sharedKeyCredentialPolicy = (SharedKeyCredentialPolicy) httpPipelinePolicy; + return sharedKeyCredentialPolicy.sharedKeyCredential(); + } + } + return null; + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/common/credentials/SASTokenCredential.java b/storage/client/blob/src/main/java/com/azure/storage/common/credentials/SASTokenCredential.java index fe93273e3f64e..4dce1c831ce21 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/common/credentials/SASTokenCredential.java +++ b/storage/client/blob/src/main/java/com/azure/storage/common/credentials/SASTokenCredential.java @@ -18,12 +18,27 @@ public final class SASTokenCredential { private static final String SIGNED_PERMISSIONS = "sp"; private static final String SIGNED_EXPIRY = "se"; private static final String SIGNATURE = "sig"; + private static final String SIGNED_RESOURCE = "sr"; // Optional SAS token pieces private static final String SIGNED_START = "st"; private static final String SIGNED_PROTOCOL = "spr"; private static final String SIGNED_IP = "sip"; + private static final String CACHE_CONTROL = "rscc"; + private static final String CONTENT_DISPOSITION = "rscd"; + private static final String CONTENT_ENCODING = "rsce"; + private static final String CONTENT_LANGUAGE = "rscl"; + private static final String CONTENT_TYPE = "rsct"; + + // Possible User Delegation Key pieces + private static final String SIGNED_KEY_O_ID = "skoid"; + private static final String SIGNED_KEY_T_ID = "sktid"; + private static final String SIGNED_KEY_START = "skt"; + private static final String SIGNED_KEY_EXPIRY = "ske"; + private static final String SIGNED_KEY_SERVICE = "sks"; + private static final String SIGNED_KEY_VERSION = "skv"; + private final String sasToken; /** @@ -57,20 +72,34 @@ public static SASTokenCredential fromQuery(String query) { queryParams.put(key, queryParam); } - if (queryParams.size() < 6 - || !queryParams.containsKey(SIGNED_VERSION) - || !queryParams.containsKey(SIGNED_SERVICES) - || !queryParams.containsKey(SIGNED_RESOURCE_TYPES) - || !queryParams.containsKey(SIGNED_PERMISSIONS) - || !queryParams.containsKey(SIGNED_EXPIRY) - || !queryParams.containsKey(SIGNATURE)) { + /* Because ServiceSAS only requires expiry and permissions, both of which could be on the container + acl, the only guaranteed indication of a SAS is the signature. We'll let the service validate + the other query parameters. */ + if (!queryParams.containsKey(SIGNATURE)) { return null; } - StringBuilder sasTokenBuilder = new StringBuilder(queryParams.get(SIGNED_VERSION)) - .append("&").append(queryParams.get(SIGNED_SERVICES)) - .append("&").append(queryParams.get(SIGNED_RESOURCE_TYPES)) - .append("&").append(queryParams.get(SIGNED_PERMISSIONS)); + StringBuilder sasTokenBuilder = new StringBuilder(); + + if (queryParams.containsKey(SIGNED_VERSION)) { + sasTokenBuilder.append(queryParams.get(SIGNED_VERSION)); + } + + if (queryParams.containsKey(SIGNED_SERVICES)) { + sasTokenBuilder.append("&").append(queryParams.get(SIGNED_SERVICES)); + } + + if (queryParams.containsKey(SIGNED_RESOURCE_TYPES)) { + sasTokenBuilder.append("&").append(queryParams.get(SIGNED_RESOURCE_TYPES)); + } + + if (queryParams.containsKey(SIGNED_PERMISSIONS)) { + sasTokenBuilder.append("&").append(queryParams.get(SIGNED_PERMISSIONS)); + } + + if (queryParams.containsKey(SIGNED_RESOURCE)) { + sasTokenBuilder.append("&").append(queryParams.get(SIGNED_RESOURCE)); + } // SIGNED_START is optional if (queryParams.containsKey(SIGNED_START)) { @@ -89,6 +118,51 @@ public static SASTokenCredential fromQuery(String query) { sasTokenBuilder.append("&").append(queryParams.get(SIGNED_PROTOCOL)); } + if (queryParams.containsKey(CACHE_CONTROL)) { + sasTokenBuilder.append("&").append(queryParams.get(CACHE_CONTROL)); + } + + if (queryParams.containsKey(CONTENT_DISPOSITION)) { + sasTokenBuilder.append("&").append(queryParams.get(CONTENT_DISPOSITION)); + } + + if (queryParams.containsKey(CONTENT_ENCODING)) { + sasTokenBuilder.append("&").append(queryParams.get(CONTENT_ENCODING)); + } + + if (queryParams.containsKey(CONTENT_LANGUAGE)) { + sasTokenBuilder.append("&").append(queryParams.get(CONTENT_LANGUAGE)); + } + + if (queryParams.containsKey(CONTENT_TYPE)) { + sasTokenBuilder.append("&").append(queryParams.get(CONTENT_TYPE)); + } + + // User Delegation Key Parameters + if (queryParams.containsKey(SIGNED_KEY_O_ID)) { + sasTokenBuilder.append("&").append(queryParams.get(SIGNED_KEY_O_ID)); + } + + if (queryParams.containsKey(SIGNED_KEY_T_ID)) { + sasTokenBuilder.append("&").append(queryParams.get(SIGNED_KEY_T_ID)); + } + + if (queryParams.containsKey(SIGNED_KEY_START)) { + sasTokenBuilder.append("&").append(queryParams.get(SIGNED_KEY_START)); + } + + if (queryParams.containsKey(SIGNED_KEY_EXPIRY)) { + sasTokenBuilder.append("&").append(queryParams.get(SIGNED_KEY_EXPIRY)); + } + + if (queryParams.containsKey(SIGNED_KEY_SERVICE)) { + sasTokenBuilder.append("&").append(queryParams.get(SIGNED_KEY_SERVICE)); + } + + if (queryParams.containsKey(SIGNED_KEY_VERSION)) { + sasTokenBuilder.append("&").append(queryParams.get(SIGNED_KEY_VERSION)); + } + sasTokenBuilder.append("&").append(queryParams.get(SIGNATURE)); return new SASTokenCredential(sasTokenBuilder.toString()); diff --git a/storage/client/blob/src/main/java/com/azure/storage/common/policy/SharedKeyCredentialPolicy.java b/storage/client/blob/src/main/java/com/azure/storage/common/policy/SharedKeyCredentialPolicy.java index 8ee1284591dd2..743eae8f262e4 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/common/policy/SharedKeyCredentialPolicy.java +++ b/storage/client/blob/src/main/java/com/azure/storage/common/policy/SharedKeyCredentialPolicy.java @@ -24,6 +24,15 @@ public SharedKeyCredentialPolicy(SharedKeyCredential credential) { this.credential = credential; } + /** + * Gets the shared key credential linked to the policy. + * @return + * The {@link SharedKeyCredential} linked to the policy. + */ + public SharedKeyCredential sharedKeyCredential() { + return this.credential; + } + @Override public Mono process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) { String authorizationValue = credential.generateAuthorizationHeader(context.httpRequest().url(), diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/SASTest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/SASTest.groovy new file mode 100644 index 0000000000000..1ebf9860f2ac3 --- /dev/null +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/SASTest.groovy @@ -0,0 +1,1079 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob + +import com.azure.storage.blob.models.AccessPolicy +import com.azure.storage.blob.models.BlobRange +import com.azure.storage.blob.models.SignedIdentifier +import com.azure.storage.blob.models.StorageErrorCode +import com.azure.storage.blob.models.UserDelegationKey +import com.azure.storage.common.credentials.SASTokenCredential +import com.azure.storage.common.credentials.SharedKeyCredential +import spock.lang.Unroll + +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset + +class SASTest extends APISpec { + + def "responseError"() { + when: + cu.listBlobsFlat() + + then: + def e = thrown(StorageException) + e.errorCode() == StorageErrorCode.INVALID_QUERY_PARAMETER_VALUE + e.statusCode() == 400 + e.message().contains("Value for one of the query parameters specified in the request URI is invalid.") + e.getMessage().contains(" Date: Fri, 12 Jul 2019 14:03:25 -0700 Subject: [PATCH 06/19] Turned on download retry test --- .../azure/storage/blob/BlobAsyncClient.java | 6 +- .../storage/blob/DownloadAsyncResponse.java | 150 ------------------ .../azure/storage/blob/DownloadResponse.java | 126 +++++---------- .../blob/models/ReliableDownloadOptions.java | 4 +- .../com/azure/storage/blob/APISpec.groovy | 31 +--- 5 files changed, 49 insertions(+), 268 deletions(-) delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/DownloadAsyncResponse.java diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java index fa4a0e77edac5..4a366c62cd4fb 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java @@ -358,7 +358,7 @@ public Mono>> download(BlobRange range, BlobAccessCond * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=upload_download "Sample code for BlobAsyncClient.download")] \n * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) */ - Mono download(BlobRange range, BlobAccessConditions accessConditions, boolean rangeGetContentMD5) { + Mono download(BlobRange range, BlobAccessConditions accessConditions, boolean rangeGetContentMD5) { range = range == null ? new BlobRange(0) : range; Boolean getMD5 = rangeGetContentMD5 ? rangeGetContentMD5 : null; accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; @@ -373,11 +373,11 @@ Mono download(BlobRange range, BlobAccessConditions acces null, null, snapshot, null, null, range.toHeaderValue(), getMD5, null, null, null, null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) - // Convert the autorest response to a DownloadAsyncResponse, which enable reliable download. + // Convert the autorest response to a DownloadResponse, which enable reliable download. .map(response -> { // If there wasn't an etag originally specified, lock on the one returned. info.eTag(response.deserializedHeaders().eTag()); - return new DownloadAsyncResponse(response, info, + return new DownloadResponse(response, info, // In the event of a stream failure, make a new request to pick up where we left off. newInfo -> this.download(new BlobRange(newInfo.offset(), newInfo.count()), diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadAsyncResponse.java b/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadAsyncResponse.java deleted file mode 100644 index e22a3fa72a16c..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadAsyncResponse.java +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.http.rest.ResponseBase; -import com.azure.storage.blob.models.BlobAccessConditions; -import com.azure.storage.blob.models.BlobDownloadHeaders; -import com.azure.storage.blob.models.BlobRange; -import com.azure.storage.blob.models.ReliableDownloadOptions; -import io.netty.buffer.ByteBuf; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.io.IOException; -import java.util.Map; -import java.util.function.Function; - -/** - * {@code DownloadAsyncResponse} wraps the protocol-layer response from {@link BlobAsyncClient#download(BlobRange, - * BlobAccessConditions, boolean, ReliableDownloadOptions)} to automatically retry failed reads from the body as - * appropriate. If the download is interrupted, the {@code DownloadAsyncResponse} will make a request to resume the download - * from where it left off, allowing the user to consume the data as one continuous stream, for any interruptions are - * hidden. The retry behavior is defined by the options passed to the {@link #body(ReliableDownloadOptions)}. The - * download will also lock on the blob's etag to ensure consistency. - *

- * Note that the retries performed as a part of this reader are composed with those of any retries in an {@link - * com.azure.core.http.HttpPipeline} used in conjunction with this reader. That is, if this object issues a request to resume a download, - * an underlying pipeline may issue several retries as a part of that request. Furthermore, this reader only retries on - * network errors; timeouts and unexpected status codes are not retried. Therefore, the behavior of this reader is - * entirely independent of and in no way coupled to an {@link com.azure.core.http.HttpPipeline}'s retry mechanism. - */ -public final class DownloadAsyncResponse { - private final HTTPGetterInfo info; - - private final ResponseBase> rawResponse; - - private final Function> getter; - - - // The constructor is package-private because customers should not be creating their own responses. - // TODO (unknown): resolve comment vs code mismatch - DownloadAsyncResponse(ResponseBase> response, - HTTPGetterInfo info, Function> getter) { - Utility.assertNotNull("getter", getter); - Utility.assertNotNull("info", info); - Utility.assertNotNull("info.eTag", info.eTag()); - this.rawResponse = response; - this.info = info; - this.getter = getter; - } - - /** - * Returns the response body which has been modified to enable reliably reading data if desired (if - * {@code options.maxRetryRequests > 0}. If retries are enabled, if a connection fails while reading, the stream - * will make additional requests to reestablish a connection and continue reading. - * - * @param options - * {@link ReliableDownloadOptions} - * - * @return A {@code Flux} which emits the data as {@code ByteBuffer}s. - */ - public Flux body(ReliableDownloadOptions options) { - ReliableDownloadOptions optionsReal = options == null ? new ReliableDownloadOptions() : options; - if (optionsReal.maxRetryRequests() == 0) { - return this.rawResponse.value(); - } - - /* - We pass -1 for currentRetryCount because we want tryContinueFlux to receive a value of 0 for number of - retries as we have not actually retried yet, only made the initial try. Because applyReliableDownload() will - add 1 before calling into tryContinueFlux, we set the initial value to -1. - */ - return this.applyReliableDownload(this.rawResponse.value(), -1, optionsReal); - } - - private Flux tryContinueFlux(Throwable t, int retryCount, ReliableDownloadOptions options) { - // If all the errors are exhausted, return this error to the user. - if (retryCount > options.maxRetryRequests() || !(t instanceof IOException)) { - return Flux.error(t); - } else { - /* - We wrap this in a try catch because we don't know the behavior of the getter. Most errors would probably - come from an unsuccessful request, which would be propagated through the onError methods. However, it is - possible the method call that returns a Single is what throws (like how our apis throw some exceptions at - call time rather than at subscription time. - */ - try { - // Get a new response and try reading from it. - return getter.apply(this.info) - .flatMapMany(response -> - /* - Do not compound the number of retries by passing in another set of downloadOptions; just get - the raw body. - */ - this.applyReliableDownload(this.rawResponse.value(), retryCount, options)); - } catch (Exception e) { - // If the getter fails, return the getter failure to the user. - return Flux.error(e); - } - } - } - - private Flux applyReliableDownload(Flux data, - int currentRetryCount, ReliableDownloadOptions options) { - return data - .doOnNext(buffer -> { - /* - Update how much data we have received in case we need to retry and propagate to the user the data we - have received. - */ - this.info.offset(this.info.offset() + buffer.readableBytes()); // was `remaining()` in Rx world - if (this.info.count() != null) { - this.info.count(this.info.count() - buffer.readableBytes()); // was `remaining()` in Rx world - } - }) - .onErrorResume(t2 -> { - // Increment the retry count and try again with the new exception. - return tryContinueFlux(t2, currentRetryCount + 1, options); - }); - } - - /** - * @return HTTP status of the download - */ - public int statusCode() { - return this.rawResponse.statusCode(); - } - - /** - * @return HTTP headers associated to the download - */ - public BlobDownloadHeaders headers() { - return this.rawResponse.deserializedHeaders(); - } - - /** - * @return all HTTP headers from the response - */ - public Map rawHeaders() { - return this.rawResponse.headers().toMap(); - } - - /** - * @return the raw response - */ - public ResponseBase> rawResponse() { - return this.rawResponse; - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadResponse.java b/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadResponse.java index 65fc6eb69c086..5ea997a72b34d 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadResponse.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadResponse.java @@ -1,104 +1,46 @@ -/* // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.azure.storage.blob; -import com.azure.storage.blob.models.BlobDownloadHeaders; -import com.azure.storage.blob.models.ReliableDownloadOptions; -import io.netty.buffer.ByteBuf; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Map; - -public class DownloadResponse { - private final DownloadAsyncResponse asyncResponse; - - DownloadResponse(DownloadAsyncResponse asyncResponse) { - this.asyncResponse = asyncResponse; - } - - */ -/** - * Gets the body of the download response. - * - * @param outputStream Stream that has the response body read into it - * @param options Options for the download - * @throws IOException If an I/O error occurs - *//* - - public void body(OutputStream outputStream, ReliableDownloadOptions options) throws IOException { - for (ByteBuf buffer : this.asyncResponse.body(options).toIterable()) { - buffer.readBytes(outputStream, buffer.readableBytes()); - buffer.release(); - } - } - - //TODO (unknown): determine signature(s) to use - */ -/*public InputStream body(ReliableDownloadOptions options) { - return new InputStream() { - - DownloadAsyncResponse response = asyncResponse; - @Override - public int read() throws IOException { - return 0; - } - }; - }*//* - -} - -*/ - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.http.HttpPipeline; import com.azure.core.http.rest.ResponseBase; import com.azure.storage.blob.models.BlobAccessConditions; import com.azure.storage.blob.models.BlobDownloadHeaders; import com.azure.storage.blob.models.BlobRange; import com.azure.storage.blob.models.ReliableDownloadOptions; +import io.netty.buffer.ByteBuf; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.time.Duration; import java.util.Map; import java.util.function.Function; /** - * {@code DownloadResponse} wraps the protocol-layer response from {@link BlobClient#download(OutputStream, - * ReliableDownloadOptions, BlobRange, BlobAccessConditions, boolean, Duration)} to automatically retry failed reads - * from the body as appropriate. If the download is interrupted, the {@code DownloadResponse} will make a request to - * resume the download from where it left off, allowing the user to consume the data as one continuous stream, for any - * interruptions are hidden. The retry behavior is defined by the options passed to the {@link #body(ReliableDownloadOptions)}. - * The download will also lock on the blob's etag to ensure consistency. + * {@code DownloadResponse} wraps the protocol-layer response from {@link BlobAsyncClient#download(BlobRange, + * BlobAccessConditions, boolean, ReliableDownloadOptions)} to automatically retry failed reads from the body as + * appropriate. If the download is interrupted, the {@code DownloadResponse} will make a request to resume the download + * from where it left off, allowing the user to consume the data as one continuous stream, for any interruptions are + * hidden. The retry behavior is defined by the options passed to the {@link #body(ReliableDownloadOptions)}. The + * download will also lock on the blob's etag to ensure consistency. *

* Note that the retries performed as a part of this reader are composed with those of any retries in an {@link - * HttpPipeline} used in conjunction with this reader. That is, if this object issues a request to resume a download, + * com.azure.core.http.HttpPipeline} used in conjunction with this reader. That is, if this object issues a request to resume a download, * an underlying pipeline may issue several retries as a part of that request. Furthermore, this reader only retries on * network errors; timeouts and unexpected status codes are not retried. Therefore, the behavior of this reader is - * entirely independent of and in no way coupled to an {@link HttpPipeline}'s retry mechanism. + * entirely independent of and in no way coupled to an {@link com.azure.core.http.HttpPipeline}'s retry mechanism. */ public final class DownloadResponse { private final HTTPGetterInfo info; - private final ResponseBase> rawResponse; + private final ResponseBase> rawResponse; private final Function> getter; // The constructor is package-private because customers should not be creating their own responses. - public DownloadResponse(ResponseBase> response, HTTPGetterInfo info, - Function> getter) { + DownloadResponse(ResponseBase> response, + HTTPGetterInfo info, Function> getter) { Utility.assertNotNull("getter", getter); Utility.assertNotNull("info", info); Utility.assertNotNull("info.eTag", info.eTag()); @@ -112,12 +54,10 @@ public DownloadResponse(ResponseBase> resp * {@code options.maxRetryRequests > 0}. If retries are enabled, if a connection fails while reading, the stream * will make additional requests to reestablish a connection and continue reading. * - * @param options - * {@link ReliableDownloadOptions} - * - * @return A {@code Flowable} which emits the data as {@code ByteBuffer}s. + * @param options {@link ReliableDownloadOptions} + * @return A {@link Flux} which emits the data as {@link ByteBuf ByteBufs} */ - public Flux body(ReliableDownloadOptions options) { + public Flux body(ReliableDownloadOptions options) { ReliableDownloadOptions optionsReal = options == null ? new ReliableDownloadOptions() : options; if (optionsReal.maxRetryRequests() == 0) { return this.rawResponse.value(); @@ -131,7 +71,7 @@ retries as we have not actually retried yet, only made the initial try. Because return this.applyReliableDownload(this.rawResponse.value(), -1, optionsReal); } - private Flux tryContinueFlux(Throwable t, int retryCount, ReliableDownloadOptions options) { + private Flux tryContinueFlux(Throwable t, int retryCount, ReliableDownloadOptions options) { // If all the errors are exhausted, return this error to the user. if (retryCount > options.maxRetryRequests() || !(t instanceof IOException)) { return Flux.error(t); @@ -143,14 +83,12 @@ possible the method call that returns a Single is what throws (like how our apis call time rather than at subscription time. */ try { - // Get a new response and try reading from it. - return getter.apply(this.info) - .flatMapMany(response -> - /* - Do not compound the number of retries by passing in another set of downloadOptions; just get - the raw body. - */ - this.applyReliableDownload(this.rawResponse.value(), retryCount, options)); + /*Get a new response and try reading from it. + + Do not compound the number of retries by passing in another set of downloadOptions; just get + the raw body. + */ + return getter.apply(this.info).flatMapMany(response -> this.applyReliableDownload(this.rawResponse.value(), retryCount, options)); } catch (Exception e) { // If the getter fails, return the getter failure to the user. return Flux.error(e); @@ -158,15 +96,15 @@ possible the method call that returns a Single is what throws (like how our apis } } - private Flux applyReliableDownload(Flux data, int currentRetryCount, ReliableDownloadOptions options) { + private Flux applyReliableDownload(Flux data, int currentRetryCount, ReliableDownloadOptions options) { return data.doOnNext(buffer -> { /* Update how much data we have received in case we need to retry and propagate to the user the data we have received. */ - this.info.offset(this.info.offset() + buffer.remaining()); + this.info.offset(this.info.offset() + buffer.readableBytes()); // was `remaining()` in Rx world if (this.info.count() != null) { - this.info.count(this.info.count() - buffer.remaining()); + this.info.count(this.info.count() - buffer.readableBytes()); // was `remaining()` in Rx world } }).onErrorResume(t2 -> { // Increment the retry count and try again with the new exception. @@ -174,19 +112,31 @@ private Flux applyReliableDownload(Flux data, int curren }); } + /** + * @return HTTP status of the download + */ public int statusCode() { return this.rawResponse.statusCode(); } + /** + * @return HTTP headers associated to the download + */ public BlobDownloadHeaders headers() { return this.rawResponse.deserializedHeaders(); } + /** + * @return all HTTP headers from the response + */ public Map rawHeaders() { return this.rawResponse.headers().toMap(); } - public ResponseBase> rawResponse() { + /** + * @return the raw response + */ + public ResponseBase> rawResponse() { return this.rawResponse; } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/models/ReliableDownloadOptions.java b/storage/client/blob/src/main/java/com/azure/storage/blob/models/ReliableDownloadOptions.java index b85680617a126..338bf25840271 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/models/ReliableDownloadOptions.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/models/ReliableDownloadOptions.java @@ -3,13 +3,13 @@ package com.azure.storage.blob.models; -import com.azure.storage.blob.DownloadAsyncResponse; +import com.azure.storage.blob.DownloadResponse; import java.util.Locale; /** * {@code ReliableDownloadOptions} contains properties which help the {@code Flux} returned from - * {@link DownloadAsyncResponse#body(ReliableDownloadOptions)} determine when to retry. + * {@link DownloadResponse#body(ReliableDownloadOptions)} determine when to retry. */ public final class ReliableDownloadOptions { private static final String PARAMETER_NOT_IN_RANGE = "The value of the parameter '%s' should be between %s and %s."; diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy index a387bbb8835a8..8d5eb9030e672 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy @@ -491,24 +491,6 @@ class APISpec extends Specification { }.request(request) } - static class MockDownloadWithRetryPolicy implements HttpPipelinePolicy { - MockDownloadWithRetryPolicy() { - } - - @Override - Mono process(HttpPipelineCallContext httpPipelineCallContext, HttpPipelineNextPolicy httpPipelineNextPolicy) { - return httpPipelineNextPolicy.process() - .flatMap { - if (it.request().headers().value("x-ms-range") != "bytes=2-6") { - return Mono.error(new IllegalArgumentException("The range header was not set correctly on retry.")) - } else { - // ETag can be a dummy value. It's not validated, but DownloadResponse requires one - return Mono.just(new MockDownloadHttpResponse(it, 206, Flux.error(new IOException()))) - } - } - } - } - /* This is for stubbing responses that will actually go through the pipeline and autorest code. Autorest does not seem to play too nicely with mocked objects and the complex reflection stuff on both ends made it more difficult to work @@ -519,10 +501,9 @@ class APISpec extends Specification { private final HttpHeaders headers private final Flux body - - MockDownloadHttpResponse(HttpResponse response, int code, Flux body) { + MockDownloadHttpResponse(HttpResponse response, int statusCode, Flux body) { this.request(response.request()) - this.statusCode = code + this.statusCode = statusCode this.headers = response.headers() this.body = body } @@ -534,7 +515,7 @@ class APISpec extends Specification { @Override String headerValue(String s) { - return null + return headers.value(s) } @Override @@ -549,17 +530,17 @@ class APISpec extends Specification { @Override Mono bodyAsByteArray() { - return Mono.just(new byte[0]) + return Mono.error(new IOException()) } @Override Mono bodyAsString() { - return Mono.just("") + return Mono.error(new IOException()) } @Override Mono bodyAsString(Charset charset) { - return Mono.just("") + return Mono.error(new IOException()) } } From bcd41b2e474a49acf99420e542a319e1dc034dd3 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Mon, 15 Jul 2019 14:43:15 -0700 Subject: [PATCH 07/19] RetryTests migrated --- .../blob/UnexpectedLengthException.java | 20 + .../common/policy/RequestRetryOptions.java | 82 ++-- .../common/policy/RequestRetryPolicy.java | 157 +++---- .../storage/blob/RequestRetryTestFactory.java | 429 ++++++++++++++++++ .../com/azure/storage/blob/RetryTest.groovy | 144 ++++++ 5 files changed, 704 insertions(+), 128 deletions(-) create mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/UnexpectedLengthException.java create mode 100644 storage/client/blob/src/test/java/com/azure/storage/blob/RequestRetryTestFactory.java create mode 100644 storage/client/blob/src/test/java/com/azure/storage/blob/RetryTest.groovy diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/UnexpectedLengthException.java b/storage/client/blob/src/main/java/com/azure/storage/blob/UnexpectedLengthException.java new file mode 100644 index 0000000000000..e3f8a52503d47 --- /dev/null +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/UnexpectedLengthException.java @@ -0,0 +1,20 @@ +package com.azure.storage.blob; + +public class UnexpectedLengthException extends IllegalStateException { + private final long bytesRead; + private final long bytesExpected; + + public UnexpectedLengthException(String message, long bytesRead, long bytesExpected) { + super(message); + this.bytesRead = bytesRead; + this.bytesExpected = bytesExpected; + } + + public long bytesRead() { + return this.bytesRead; + } + + public long bytesExpected() { + return this.bytesExpected; + } +} diff --git a/storage/client/blob/src/main/java/com/azure/storage/common/policy/RequestRetryOptions.java b/storage/client/blob/src/main/java/com/azure/storage/common/policy/RequestRetryOptions.java index 80b157bff4716..7e6ae9bcf3dbb 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/common/policy/RequestRetryOptions.java +++ b/storage/client/blob/src/main/java/com/azure/storage/common/policy/RequestRetryOptions.java @@ -6,9 +6,9 @@ import java.util.concurrent.TimeUnit; /** - * Options for configuring the {@link RequestRetryPolicy}. Please refer to the Factory for more information. Note - * that there is no option for overall operation timeout. This is because Rx object have a timeout field which provides - * this functionality. + * Options for configuring the {@link RequestRetryPolicy}. Please refer to the Factory for more information. Note that + * there is no option for overall operation timeout. This is because Rx object have a timeout field which provides this + * functionality. */ public final class RequestRetryOptions { @@ -28,47 +28,43 @@ public final class RequestRetryOptions { */ public RequestRetryOptions() { this(RetryPolicyType.EXPONENTIAL, null, - null, null, null, null); + null, null, null, null); } /** * Configures how the {@link com.azure.core.http.HttpPipeline} should retry requests. * - * @param retryPolicyType - * A {@link RetryPolicyType} specifying the type of retry pattern to use. A value of {@code null} accepts - * the default. - * @param maxTries - * Specifies the maximum number of attempts an operation will be tried before producing an error. A value of - * {@code null} means that you accept our default policy. A value of 1 means 1 try and no retries. - * @param tryTimeout - * Indicates the maximum time allowed for any single try of an HTTP request. A value of {@code null} means - * that you accept our default. NOTE: When transferring large amounts of data, the default TryTimeout will - * probably not be sufficient. You should override this value based on the bandwidth available to the host - * machine and proximity to the Storage service. A good starting point may be something like (60 seconds per - * MB of anticipated-payload-size). - * @param retryDelayInMs - * Specifies the amount of delay to use before retrying an operation. A value of {@code null} means you - * accept the default value. The delay increases (exponentially or linearly) with each retry up to a maximum - * specified by MaxRetryDelay. If you specify {@code null}, then you must also specify {@code null} for - * MaxRetryDelay. - * @param maxRetryDelayInMs - * Specifies the maximum delay allowed before retrying an operation. A value of {@code null} means you - * accept the default value. If you specify {@code null}, then you must also specify {@code null} for - * RetryDelay. - * @param secondaryHost - * If a secondaryHost is specified, retries will be tried against this host. If secondaryHost is - * {@code null} (the default) then operations are not retried against another host. NOTE: Before setting - * this field, make sure you understand the issues around reading stale and potentially-inconsistent data at - * this webpage - * @throws IllegalArgumentException If {@code retryDelayInMs} and {@code maxRetryDelayInMs} are not both null or non-null - * or {@code retryPolicyType} isn't {@link RetryPolicyType#EXPONENTIAL} or {@link RetryPolicyType#FIXED}. + * @param retryPolicyType A {@link RetryPolicyType} specifying the type of retry pattern to use. A value of {@code + * null} accepts the default. + * @param maxTries Specifies the maximum number of attempts an operation will be tried before producing an error. A + * value of {@code null} means that you accept our default policy. A value of 1 means 1 try and no retries. + * @param tryTimeout Indicates the maximum time allowed for any single try of an HTTP request. A value of {@code + * null} means that you accept our default. NOTE: When transferring large amounts of data, the default TryTimeout + * will probably not be sufficient. You should override this value based on the bandwidth available to the host + * machine and proximity to the Storage service. A good starting point may be something like (60 seconds per MB of + * anticipated-payload-size). + * @param retryDelayInMs Specifies the amount of delay to use before retrying an operation. A value of {@code null} + * means you accept the default value. The delay increases (exponentially or linearly) with each retry up to a + * maximum specified by MaxRetryDelay. If you specify {@code null}, then you must also specify {@code null} for + * MaxRetryDelay. + * @param maxRetryDelayInMs Specifies the maximum delay allowed before retrying an operation. A value of {@code + * null} means you accept the default value. If you specify {@code null}, then you must also specify {@code null} + * for RetryDelay. + * @param secondaryHost If a secondaryHost is specified, retries will be tried against this host. If secondaryHost + * is {@code null} (the default) then operations are not retried against another host. NOTE: Before setting this + * field, make sure you understand the issues around reading stale and potentially-inconsistent data at + * this + * webpage + * @throws IllegalArgumentException If {@code retryDelayInMs} and {@code maxRetryDelayInMs} are not both null or + * non-null or {@code retryPolicyType} isn't {@link RetryPolicyType#EXPONENTIAL} or {@link RetryPolicyType#FIXED}. * *

Sample Code

* - *

For more samples, please see the samples file

+ *

For more samples, please see the samples + * file

*/ public RequestRetryOptions(RetryPolicyType retryPolicyType, Integer maxTries, Integer tryTimeout, - Long retryDelayInMs, Long maxRetryDelayInMs, String secondaryHost) { + Long retryDelayInMs, Long maxRetryDelayInMs, String secondaryHost) { this.retryPolicyType = retryPolicyType == null ? RetryPolicyType.EXPONENTIAL : retryPolicyType; if (maxTries != null) { assertInBounds("maxRetries", maxTries, 1, Integer.MAX_VALUE); @@ -85,7 +81,7 @@ public RequestRetryOptions(RetryPolicyType retryPolicyType, Integer maxTries, In } if ((retryDelayInMs == null && maxRetryDelayInMs != null) - || (retryDelayInMs != null && maxRetryDelayInMs == null)) { + || (retryDelayInMs != null && maxRetryDelayInMs == null)) { throw new IllegalArgumentException("Both retryDelay and maxRetryDelay must be null or neither can be null"); } @@ -111,36 +107,34 @@ public RequestRetryOptions(RetryPolicyType retryPolicyType, Integer maxTries, In this.secondaryHost = secondaryHost; } - int maxTries() { + public int maxTries() { return this.maxTries; } - int tryTimeout() { + public int tryTimeout() { return this.tryTimeout; } - String secondaryHost() { + public String secondaryHost() { return this.secondaryHost; } - long retryDelayInMs() { + public long retryDelayInMs() { return retryDelayInMs; } - long maxRetryDelayInMs() { + public long maxRetryDelayInMs() { return maxRetryDelayInMs; } /** * Calculates how long to delay before sending the next request. * - * @param tryCount - * An {@code int} indicating which try we are on. - * + * @param tryCount An {@code int} indicating which try we are on. * @return A {@code long} value of how many milliseconds to delay. */ long calculateDelayInMs(int tryCount) { - long delay = 0; + long delay; switch (this.retryPolicyType) { case EXPONENTIAL: delay = (pow(2L, tryCount - 1) - 1L) * this.retryDelayInMs; diff --git a/storage/client/blob/src/main/java/com/azure/storage/common/policy/RequestRetryPolicy.java b/storage/client/blob/src/main/java/com/azure/storage/common/policy/RequestRetryPolicy.java index 774a44999ce76..83bef0b2b49fd 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/common/policy/RequestRetryPolicy.java +++ b/storage/client/blob/src/main/java/com/azure/storage/common/policy/RequestRetryPolicy.java @@ -22,10 +22,10 @@ import java.util.concurrent.TimeoutException; /** - * This is a request policy in an {@link com.azure.core.http.HttpPipeline} for retrying a given HTTP request. The request - * that is retried will be identical each time it is reissued. Retries will try against a secondary if one is specified - * and the type of operation/error indicates that the secondary can handle the request. Exponential and fixed backoff are - * supported. The policy must only be used directly when creating a custom pipeline. + * This is a request policy in an {@link com.azure.core.http.HttpPipeline} for retrying a given HTTP request. The + * request that is retried will be identical each time it is reissued. Retries will try against a secondary if one is + * specified and the type of operation/error indicates that the secondary can handle the request. Exponential and fixed + * backoff are supported. The policy must only be used directly when creating a custom pipeline. */ public final class RequestRetryPolicy implements HttpPipelinePolicy { private final RequestRetryOptions requestRetryOptions; @@ -41,40 +41,32 @@ public RequestRetryPolicy(RequestRetryOptions requestRetryOptions) { @Override public Mono process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) { - HttpRequest httpRequest = context.httpRequest(); - boolean considerSecondary = (httpRequest.httpMethod().equals(HttpMethod.GET) - || httpRequest.httpMethod().equals(HttpMethod.HEAD)) - && (this.requestRetryOptions.secondaryHost() != null); + boolean considerSecondary = (this.requestRetryOptions.secondaryHost() != null) + && (HttpMethod.GET.equals(context.httpRequest().httpMethod()) || HttpMethod.HEAD.equals(context.httpRequest().httpMethod())); - return this.attemptAsync(httpRequest, next, 1, considerSecondary, 1); + return attemptAsync(context, next, context.httpRequest(), considerSecondary, 1, 1); } /** - * This method actually attempts to send the request and determines if we should attempt again and, if so, how - * long to wait before sending out the next request. + * This method actually attempts to send the request and determines if we should attempt again and, if so, how long + * to wait before sending out the next request. *

- * Exponential retry algorithm: ((2 ^ attempt) - 1) * delay * random(0.8, 1.2) When to retry: connection failure - * or an HTTP status code of 500 or greater, except 501 and 505 If using a secondary: Odd tries go against - * primary; even tries go against the secondary For a primary wait ((2 ^ primaryTries - 1) * delay * random(0.8, - * 1.2) If secondary gets a 404, don't fail, retry but future retries are only against the primary When retrying - * against a secondary, ignore the retry count and wait (.1 second * random(0.8, 1.2)) + * Exponential retry algorithm: ((2 ^ attempt) - 1) * delay * random(0.8, 1.2) When to retry: connection failure or + * an HTTP status code of 500 or greater, except 501 and 505 If using a secondary: Odd tries go against primary; + * even tries go against the secondary For a primary wait ((2 ^ primaryTries - 1) * delay * random(0.8, 1.2) If + * secondary gets a 404, don't fail, retry but future retries are only against the primary When retrying against a + * secondary, ignore the retry count and wait (.1 second * random(0.8, 1.2)) * - * @param httpRequest - * The request to try. - * @param primaryTry - * This indicates how man tries we've attempted against the primary DC. - * @param considerSecondary - * Before each try, we'll select either the primary or secondary URL if appropriate. - * @param attempt - * This indicates the total number of attempts to send the request. - * - * @return A single containing either the successful response or an error that was not retryable because either - * the maxTries was exceeded or retries will not mitigate the issue. + * @param context The request to try. + * @param next The next policy to apply to the request + * @param originalRequest The unmodified original request + * @param primaryTry This indicates how man tries we've attempted against the primary DC. + * @param attempt This indicates the total number of attempts to send the request. + * @return A single containing either the successful response or an error that was not retryable because either the + * maxTries was exceeded or retries will not mitigate the issue. */ - private Mono attemptAsync(final HttpRequest httpRequest, HttpPipelineNextPolicy next, final int primaryTry, - final boolean considerSecondary, - final int attempt) { - + private Mono attemptAsync(final HttpPipelineCallContext context, HttpPipelineNextPolicy next, final HttpRequest originalRequest, + boolean considerSecondary, final int primaryTry, final int attempt) { // Determine which endpoint to try. It's primary if there is no secondary or if it is an odd number attempt. final boolean tryingPrimary = !considerSecondary || (attempt % 2 != 0); @@ -96,14 +88,14 @@ stream, the buffers that were emitted will have already been consumed (their pos ByteBuffers downstream will only actually consume a duplicate so the original is preserved. This only duplicates the ByteBuffer object, not the underlying data. */ - Flux bufferedBody = httpRequest.body() == null - ? null : httpRequest.body().map(ByteBuf::duplicate); - httpRequest.body(bufferedBody); + context.httpRequest(originalRequest.buffer()); + Flux bufferedBody = (context.httpRequest().body() == null) ? null : context.httpRequest().body().map(ByteBuf::duplicate); + context.httpRequest().body(bufferedBody); if (!tryingPrimary) { - UrlBuilder builder = UrlBuilder.parse(httpRequest.url()); + UrlBuilder builder = UrlBuilder.parse(context.httpRequest().url()); builder.host(this.requestRetryOptions.secondaryHost()); try { - httpRequest.url(builder.toURL()); + context.httpRequest().url(builder.toURL()); } catch (MalformedURLException e) { return Mono.error(e); } @@ -114,55 +106,53 @@ stream, the buffers that were emitted will have already been consumed (their pos until after the retry backoff delay, so we call delaySubscription. */ return next.clone().process() - .timeout(Duration.ofSeconds(this.requestRetryOptions.tryTimeout())) - .delaySubscription(Duration.ofMillis(delayMs)) - .flatMap(response -> { - boolean newConsiderSecondary = considerSecondary; - String action; - int statusCode = response.statusCode(); + .timeout(Duration.ofSeconds(this.requestRetryOptions.tryTimeout())) + .delaySubscription(Duration.ofMillis(delayMs)) + .flatMap(response -> { + boolean newConsiderSecondary = considerSecondary; + String action; + int statusCode = response.statusCode(); /* If attempt was against the secondary & it returned a StatusNotFound (404), then the resource was not found. This may be due to replication delay. So, in this case, we'll never try the secondary again for this operation. */ - if (!tryingPrimary && statusCode == 404) { - newConsiderSecondary = false; - action = "Retry: Secondary URL returned 404"; - } else if (statusCode == 503 || statusCode == 500) { - action = "Retry: Temporary error or server timeout"; - } else { - action = "NoRetry: Successful HTTP request"; - } - - if (action.charAt(0) == 'R' && attempt < requestRetryOptions.maxTries()) { + if (!tryingPrimary && statusCode == 404) { + newConsiderSecondary = false; + action = "Retry: Secondary URL returned 404"; + } else if (statusCode == 503 || statusCode == 500) { + action = "Retry: Temporary error or server timeout"; + } else { + action = "NoRetry: Successful HTTP request"; + } + + if (action.charAt(0) == 'R' && attempt < requestRetryOptions.maxTries()) { /* We increment primaryTry if we are about to try the primary again (which is when we consider the secondary and tried the secondary this time (tryingPrimary==false) or we do not consider the secondary at all (considerSecondary==false)). This will ensure primaryTry is correct when passed to calculate the delay. */ - int newPrimaryTry = !tryingPrimary || !considerSecondary - ? primaryTry + 1 : primaryTry; - return attemptAsync(httpRequest, next, newPrimaryTry, newConsiderSecondary, - attempt + 1); - } - return Mono.just(response); - }) - .onErrorResume(throwable -> { + int newPrimaryTry = (!tryingPrimary || !considerSecondary) ? primaryTry + 1 : primaryTry; + return attemptAsync(context, next, originalRequest, newConsiderSecondary, newPrimaryTry, attempt + 1); + } + + return Mono.just(response); + }).onErrorResume(throwable -> { /* It is likely that many users will not realize that their Flux must be replayable and get an error upon retries when the provided data length does not match the length of the exact data. We cannot enforce the desired Flux behavior, so we provide a hint when this is likely the root cause. */ - if (throwable instanceof IllegalStateException && attempt > 1) { - return Mono.error(new IllegalStateException("The request failed because the " - + "size of the contents of the provided Flux did not match the provided " - + "data size upon attempting to retry. This is likely caused by the Flux " - + "not being replayable. To support retries, all Fluxes must produce the " - + "same data for each subscriber. Please ensure this behavior.", throwable)); - } + if (throwable instanceof IllegalStateException && attempt > 1) { + return Mono.error(new IllegalStateException("The request failed because the " + + "size of the contents of the provided Flux did not match the provided " + + "data size upon attempting to retry. This is likely caused by the Flux " + + "not being replayable. To support retries, all Fluxes must produce the " + + "same data for each subscriber. Please ensure this behavior.", throwable)); + } /* IOException is a catch-all for IO related errors. Technically it includes many types which may @@ -170,28 +160,27 @@ we do not consider the secondary at all (considerSecondary==false)). This will either case, it is better to optimistically retry instead of failing too soon. A Timeout Exception is a client-side timeout coming from Rx. */ - String action; - if (throwable instanceof IOException) { - action = "Retry: Network error"; - } else if (throwable instanceof TimeoutException) { - action = "Retry: Client timeout"; - } else { - action = "NoRetry: Unknown error"; - } - - if (action.charAt(0) == 'R' && attempt < requestRetryOptions.maxTries()) { + String action; + if (throwable instanceof IOException) { + action = "Retry: Network error"; + } else if (throwable instanceof TimeoutException) { + action = "Retry: Client timeout"; + } else { + action = "NoRetry: Unknown error"; + } + + if (action.charAt(0) == 'R' && attempt < requestRetryOptions.maxTries()) { /* We increment primaryTry if we are about to try the primary again (which is when we consider the secondary and tried the secondary this time (tryingPrimary==false) or we do not consider the secondary at all (considerSecondary==false)). This will ensure primaryTry is correct when passed to calculate the delay. */ - int newPrimaryTry = !tryingPrimary || !considerSecondary - ? primaryTry + 1 : primaryTry; - return attemptAsync(httpRequest, next, newPrimaryTry, considerSecondary, - attempt + 1); - } - return Mono.error(throwable); - }); + int newPrimaryTry = (!tryingPrimary || !considerSecondary) ? primaryTry + 1 : primaryTry; + return attemptAsync(context, next, originalRequest, considerSecondary, newPrimaryTry, attempt + 1); + } + + return Mono.error(throwable); + }); } } diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/RequestRetryTestFactory.java b/storage/client/blob/src/test/java/com/azure/storage/blob/RequestRetryTestFactory.java new file mode 100644 index 0000000000000..80a1647609234 --- /dev/null +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/RequestRetryTestFactory.java @@ -0,0 +1,429 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob; + +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpPipeline; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import com.azure.core.http.ProxyOptions; +import com.azure.core.implementation.http.UrlBuilder; +import com.azure.storage.common.policy.RequestRetryOptions; +import com.azure.storage.common.policy.RequestRetryPolicy; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import reactor.core.Disposable; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.Charset; +import java.time.Duration; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Supplier; + +import static java.lang.StrictMath.pow; + +public class RequestRetryTestFactory { + static final int RETRY_TEST_SCENARIO_RETRY_UNTIL_SUCCESS = 1; + + static final int RETRY_TEST_SCENARIO_RETRY_UNTIL_MAX_RETRIES = 2; + + static final int RETRY_TEST_SCENARIO_NON_RETRYABLE = 3; + + static final int RETRY_TEST_SCENARIO_NON_RETRYABLE_SECONDARY = 4; + + static final int RETRY_TEST_SCENARIO_NETWORK_ERROR = 5; + + static final int RETRY_TEST_SCENARIO_EXPONENTIAL_TIMING = 6; + + static final int RETRY_TEST_SCENARIO_FIXED_TIMING = 7; + + static final int RETRY_TEST_SCENARIO_TRY_TIMEOUT = 8; + + static final int RETRY_TEST_SCENARIO_NON_REPLAYABLE_FLOWABLE = 9; + + // Cancelable + + static final String RETRY_TEST_PRIMARY_HOST = "PrimaryDC"; + + static final String RETRY_TEST_SECONDARY_HOST = "SecondaryDC"; + static final ByteBuf RETRY_TEST_DEFAULT_DATA = Unpooled.wrappedBuffer("Default data".getBytes()); + private static final String RETRY_TEST_HEADER = "TestHeader"; + private static final String RETRY_TEST_QUERY_PARAM = "TestQueryParam"; + private static final Mono RETRY_TEST_OK_RESPONSE = Mono.just(new RetryTestResponse(200)); + + /* + We wrap the response in a StorageErrorException to mock the HttpClient. Any responses that the HttpClient receives + that is not an expected response is wrapped in a StorageErrorException. + */ + private static final Mono RETRY_TEST_TEMPORARY_ERROR_RESPONSE = Mono.just(new RetryTestResponse(503)); + + private static final Mono RETRY_TEST_TIMEOUT_ERROR_RESPONSE = Mono.just(new RetryTestResponse(500)); + + private static final Mono RETRY_TEST_NON_RETRYABLE_ERROR = Mono.just(new RetryTestResponse(400)); + + private static final Mono RETRY_TEST_NOT_FOUND_RESPONSE = Mono.just(new RetryTestResponse(404)); + + private int retryTestScenario; + + private RequestRetryOptions options; + + /* + It is atypical and not recommended to have mutable state on the factory itself. However, the tests will need to + be able to validate the number of tries, and the tests will not have access to the policies, so we break our own + rule here. + */ + private int tryNumber; + + private OffsetDateTime time; + + public RequestRetryTestFactory(int scenario, RequestRetryOptions options) { + this.retryTestScenario = scenario; + this.options = options; + } + + Mono send(URL url) { + return HttpPipeline.builder() + .policies(new RequestRetryPolicy(this.options)) + .httpClient(new RetryTestClient(this)) + .build() + .send(new HttpRequest(HttpMethod.GET, url).body(Flux.just(RETRY_TEST_DEFAULT_DATA))); + } + + public int getTryNumber() { + return this.tryNumber; + } + + // The retry factory only really cares about the status code. + private static final class RetryTestResponse extends HttpResponse { + int statusCode; + + RetryTestResponse(int statusCode) { + this.statusCode = statusCode; + } + + @Override + public int statusCode() { + return this.statusCode; + } + + @Override + public String headerValue(String headerName) { + return null; + } + + @Override + public HttpHeaders headers() { + return null; + } + + @Override + public Flux body() { + return null; + } + + @Override + public Mono bodyAsByteArray() { + return null; + } + + @Override + public Mono bodyAsString() { + return null; + } + + @Override + public Mono bodyAsString(Charset charset) { + return null; + } + } + + private final class RetryTestClient implements HttpClient { + private RequestRetryTestFactory factory; + + RetryTestClient(RequestRetryTestFactory parent) { + this.factory = parent; + } + + @Override + public Mono send(HttpRequest request) { + this.factory.tryNumber++; + if (this.factory.tryNumber > this.factory.options.maxTries()) { + throw new IllegalArgumentException("Try number has exceeded max tries"); + } + + // Validate the expected preconditions for each try: The correct host is used. + String expectedHost = RETRY_TEST_PRIMARY_HOST; + if (this.factory.tryNumber % 2 == 0) { + /* + Special cases: retry until success scenario fail's on the 4th try with a 404 on the secondary, so we + never expect it to check the secondary after that. All other tests should continue to check the + secondary. + Exponential timing only tests secondary backoff once but uses the rest of the retries to hit the max + delay. + */ + if (!((this.factory.retryTestScenario == RequestRetryTestFactory.RETRY_TEST_SCENARIO_RETRY_UNTIL_SUCCESS && this.factory.tryNumber > 4) + || (this.factory.retryTestScenario == RequestRetryTestFactory.RETRY_TEST_SCENARIO_EXPONENTIAL_TIMING && this.factory.tryNumber > 2))) { + expectedHost = RETRY_TEST_SECONDARY_HOST; + } + } + + if (!request.url().getHost().equals(expectedHost)) { + throw new IllegalArgumentException("The host does not match the expected host"); + } + + /* + This policy will add test headers and query parameters. Ensure they are removed/reset for each retry. + The retry policy should be starting with a fresh copy of the request for every try. + */ + if (request.headers().value(RETRY_TEST_HEADER) != null) { + throw new IllegalArgumentException("Headers not reset."); + } + if ((request.url().getQuery() != null && request.url().getQuery().contains(RETRY_TEST_QUERY_PARAM))) { + throw new IllegalArgumentException("Query params not reset."); + } + + // Subscribe and block until all information is read to prevent a blocking on another thread exception from Reactor. + ByteBuf buf = Unpooled.buffer(); + Disposable disposable = request.body().subscribe(buf::writeBytes); + while (!disposable.isDisposed()) { + } + if (RETRY_TEST_DEFAULT_DATA.compareTo(buf) != 0) { + throw new IllegalArgumentException(("Body not reset.")); + } + + /* + Modify the request as policies downstream of the retry policy are likely to do. These must be reset on each + try. + */ + request.headers().put(RETRY_TEST_HEADER, "testheader"); + UrlBuilder builder = UrlBuilder.parse(request.url()); + builder.setQueryParameter(RETRY_TEST_QUERY_PARAM, "testquery"); + try { + request.url(builder.toURL()); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("The URL has been mangled"); + } + + switch (this.factory.retryTestScenario) { + case RETRY_TEST_SCENARIO_RETRY_UNTIL_SUCCESS: + switch (this.factory.tryNumber) { + case 1: + /* + The timer is set with a timeout on the Mono used to make the request. If the Mono + doesn't return success fast enough, it will throw a TimeoutException. We can short circuit + the waiting by simply returning an error. We will validate the time parameter later. Here, + we just test that a timeout is retried. + */ + return Mono.error(new TimeoutException()); + case 2: + return RETRY_TEST_TEMPORARY_ERROR_RESPONSE; + case 3: + return RETRY_TEST_TIMEOUT_ERROR_RESPONSE; + case 4: + /* + By returning 404 when we should be testing against the secondary, we exercise the logic + that should prevent further tries to secondary when the secondary evidently doesn't have the + data. + */ + return RETRY_TEST_NOT_FOUND_RESPONSE; + case 5: + // Just to get to a sixth try where we ensure we should not be trying the secondary again. + return RETRY_TEST_TEMPORARY_ERROR_RESPONSE; + case 6: + return RETRY_TEST_OK_RESPONSE; + default: + throw new IllegalArgumentException("Continued trying after success."); + } + + case RETRY_TEST_SCENARIO_RETRY_UNTIL_MAX_RETRIES: + return RETRY_TEST_TEMPORARY_ERROR_RESPONSE; + + case RETRY_TEST_SCENARIO_NON_RETRYABLE: + if (this.factory.tryNumber == 1) { + return RETRY_TEST_NON_RETRYABLE_ERROR; + } else { + throw new IllegalArgumentException("Continued trying after non retryable error."); + } + + case RETRY_TEST_SCENARIO_NON_RETRYABLE_SECONDARY: + switch (this.factory.tryNumber) { + case 1: + return RETRY_TEST_TEMPORARY_ERROR_RESPONSE; + case 2: + return RETRY_TEST_NON_RETRYABLE_ERROR; + default: + throw new IllegalArgumentException("Continued trying after non retryable error."); + } + + case RETRY_TEST_SCENARIO_NETWORK_ERROR: + switch (this.factory.tryNumber) { + case 1: + // fall through + case 2: + return Mono.error(new IOException()); + case 3: + return RETRY_TEST_OK_RESPONSE; + default: + throw new IllegalArgumentException("Continued retrying after success."); + } + + case RETRY_TEST_SCENARIO_TRY_TIMEOUT: + switch (this.factory.tryNumber) { + case 1: + case 2: + return RETRY_TEST_OK_RESPONSE.delaySubscription(Duration.ofSeconds(options.tryTimeout() + 1)); + case 3: + return RETRY_TEST_OK_RESPONSE.delaySubscription(Duration.ofSeconds(options.tryTimeout() - 1)); + default: + throw new IllegalArgumentException("Continued retrying after success"); + } + + case RETRY_TEST_SCENARIO_EXPONENTIAL_TIMING: + switch (this.factory.tryNumber) { + case 1: + this.factory.time = OffsetDateTime.now(); + return RETRY_TEST_TEMPORARY_ERROR_RESPONSE; + case 2: + /* + Calculation for secondary is always the same, so we don't need to keep testing it. Not + trying the secondary any more will also speed up the test. + */ + return testDelayBounds(1, false, RETRY_TEST_NOT_FOUND_RESPONSE); + case 3: + return testDelayBounds(2, true, RETRY_TEST_TEMPORARY_ERROR_RESPONSE); + case 4: + return testDelayBounds(3, true, RETRY_TEST_TEMPORARY_ERROR_RESPONSE); + case 5: + /* + With the current configuration in RetryTest, the maxRetryDelay should be reached upon the + fourth try to the primary. + */ + return testMaxDelayBounds(RETRY_TEST_TEMPORARY_ERROR_RESPONSE); + case 6: + return testMaxDelayBounds(RETRY_TEST_OK_RESPONSE); + default: + throw new IllegalArgumentException("Max retries exceeded/continued retrying after success"); + } + + case RETRY_TEST_SCENARIO_FIXED_TIMING: + switch (this.factory.tryNumber) { + case 1: + this.factory.time = OffsetDateTime.now(); + return RETRY_TEST_TEMPORARY_ERROR_RESPONSE; + case 2: + return testDelayBounds(1, false, RETRY_TEST_TEMPORARY_ERROR_RESPONSE); + case 3: + return testDelayBounds(2, true, RETRY_TEST_TEMPORARY_ERROR_RESPONSE); + case 4: + /* + Fixed backoff means it's always the same and we never hit the max, no need to keep testing. + */ + return RETRY_TEST_OK_RESPONSE; + default: + throw new IllegalArgumentException("Retries continued after success."); + } + + case RETRY_TEST_SCENARIO_NON_REPLAYABLE_FLOWABLE: + switch (this.factory.tryNumber) { + case 1: + return RETRY_TEST_TEMPORARY_ERROR_RESPONSE; + case 2: + return Mono.error(new UnexpectedLengthException("Unexpected length", 5, 6)); + default: + throw new IllegalArgumentException("Retries continued on non retryable error."); + } + default: + throw new IllegalArgumentException("Invalid retry test scenario."); + } + } + + @Override + public HttpClient proxy(Supplier supplier) { + return null; + } + + @Override + public HttpClient wiretap(boolean b) { + return null; + } + + @Override + public HttpClient port(int i) { + return null; + } + + /* + Calculate the delay in seconds. Round up to ensure we include the maximum value and some offset for the code + executing between the original calculation in the retry policy and this check. + */ + private long calcPrimaryDelay(int tryNumber) { + switch (this.factory.retryTestScenario) { + case RETRY_TEST_SCENARIO_EXPONENTIAL_TIMING: + return (long) Math.ceil( + ((pow(2L, tryNumber - 1) - 1L) * this.factory.options.retryDelayInMs()) / 1000); + case RETRY_TEST_SCENARIO_FIXED_TIMING: + return (long) Math.ceil(this.factory.options.retryDelayInMs() / 1000); + default: + throw new IllegalArgumentException("Invalid test scenario"); + } + } + + private OffsetDateTime calcUpperBound(OffsetDateTime start, int primaryTryNumber, boolean tryingPrimary) { + if (tryingPrimary) { + return start.plus(calcPrimaryDelay(primaryTryNumber) * 1000 + 500, ChronoUnit.MILLIS); + } else { + return start.plus(1400, ChronoUnit.MILLIS); + } + } + + private OffsetDateTime calcLowerBound(OffsetDateTime start, int primaryTryNumber, boolean tryingPrimary) { + if (tryingPrimary) { + return start.plus(calcPrimaryDelay(primaryTryNumber) * 1000 - 500, ChronoUnit.MILLIS); + } else { + return start.plus(700, ChronoUnit.MILLIS); + } + } + + private Mono testDelayBounds(int primaryTryNumber, boolean tryingPrimary, Mono response) { + /* + We have to return a new Mono so that the calculation for time is performed at the correct time, i.e. when + the Mono is actually subscribed to. This mocks an HttpClient because the requests are made only when + the Mono is subscribed to, not when all the infrastructure around it is put in place, and we care about + the delay before the request itself. + */ + return Mono.defer(() -> Mono.fromCallable(() -> { + OffsetDateTime now = OffsetDateTime.now(); + if (now.isAfter(calcUpperBound(factory.time, primaryTryNumber, tryingPrimary)) + || now.isBefore(calcLowerBound(factory.time, primaryTryNumber, tryingPrimary))) { + throw new IllegalArgumentException("Delay was not within jitter bounds"); + } + + factory.time = now; + return response.block(); + })); + } + + private Mono testMaxDelayBounds(Mono response) { + return Mono.defer(() -> Mono.fromCallable(() -> { + OffsetDateTime now = OffsetDateTime.now(); + if (now.isAfter(factory.time.plusSeconds((long) Math.ceil((factory.options.maxRetryDelayInMs() / 1000) + 1)))) { + throw new IllegalArgumentException("Max retry delay exceeded"); + } else if (now.isBefore(factory.time.plusSeconds((long) Math.ceil((factory.options.maxRetryDelayInMs() / 1000) - 1)))) { + throw new IllegalArgumentException("Retry did not delay long enough"); + } + + factory.time = now; + return response.block(); + })); + } + } +} diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/RetryTest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/RetryTest.groovy new file mode 100644 index 0000000000000..ab5f9aabd8726 --- /dev/null +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/RetryTest.groovy @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob + + +import com.azure.core.http.HttpResponse +import com.azure.storage.common.policy.RequestRetryOptions +import com.azure.storage.common.policy.RetryPolicyType +import spock.lang.Unroll +// Tests for package-private functionality. +class RetryTest extends APISpec { + static URL retryTestURL = new URL("https://" + RequestRetryTestFactory.RETRY_TEST_PRIMARY_HOST) + static RequestRetryOptions retryTestOptions = new RequestRetryOptions(RetryPolicyType.EXPONENTIAL, 6, 2, + 1000L, 4000L, RequestRetryTestFactory.RETRY_TEST_SECONDARY_HOST) + + def "Retries until success"() { + setup: + RequestRetryTestFactory retryTestFactory = new RequestRetryTestFactory(RequestRetryTestFactory.RETRY_TEST_SCENARIO_RETRY_UNTIL_SUCCESS, retryTestOptions) + + when: + HttpResponse response = retryTestFactory.send(retryTestURL).block() + + then: + response.statusCode() == 200 + retryTestFactory.getTryNumber() == 6 + } + + def "Retries until max retries"() { + setup: + RequestRetryTestFactory retryTestFactory = new RequestRetryTestFactory(RequestRetryTestFactory.RETRY_TEST_SCENARIO_RETRY_UNTIL_MAX_RETRIES, retryTestOptions) + + when: + HttpResponse response = retryTestFactory.send(retryTestURL).block() + + then: + response.statusCode() == 503 + retryTestFactory.getTryNumber() == retryTestOptions.maxTries() + } + + def "Retries non retryable"() { + setup: + RequestRetryTestFactory retryTestFactory = new RequestRetryTestFactory(RequestRetryTestFactory.RETRY_TEST_SCENARIO_NON_RETRYABLE, retryTestOptions) + + when: + HttpResponse response = retryTestFactory.send(retryTestURL).block() + + then: + response.statusCode() == 400 + retryTestFactory.getTryNumber() == 1 + } + + def "Retries non retryable secondary"() { + setup: + RequestRetryTestFactory retryTestFactory = new RequestRetryTestFactory(RequestRetryTestFactory.RETRY_TEST_SCENARIO_NON_RETRYABLE_SECONDARY, retryTestOptions) + + when: + HttpResponse response = retryTestFactory.send(retryTestURL).block() + + then: + response.statusCode() == 400 + retryTestFactory.getTryNumber() == 2 + } + + def "Retries network error"() { + setup: + RequestRetryTestFactory retryTestFactory = new RequestRetryTestFactory(RequestRetryTestFactory.RETRY_TEST_SCENARIO_NETWORK_ERROR, retryTestOptions) + + when: + HttpResponse response = retryTestFactory.send(retryTestURL).block() + + then: + response.statusCode() == 200 + retryTestFactory.getTryNumber() == 3 + } + + def "Retries try timeout"() { + setup: + RequestRetryTestFactory retryTestFactory = new RequestRetryTestFactory(RequestRetryTestFactory.RETRY_TEST_SCENARIO_TRY_TIMEOUT, retryTestOptions) + + when: + HttpResponse response = retryTestFactory.send(retryTestURL).block() + + then: + response.statusCode() == 200 + retryTestFactory.getTryNumber() == 3 + } + + def "Retries exponential delay"() { + setup: + RequestRetryTestFactory retryTestFactory = new RequestRetryTestFactory(RequestRetryTestFactory.RETRY_TEST_SCENARIO_EXPONENTIAL_TIMING, retryTestOptions) + + when: + HttpResponse response = retryTestFactory.send(retryTestURL).block() + + then: + response.statusCode() == 200 + retryTestFactory.getTryNumber() == 6 + } + + def "Retries fixed delay"() { + setup: + RequestRetryTestFactory retryTestFactory = new RequestRetryTestFactory(RequestRetryTestFactory.RETRY_TEST_SCENARIO_FIXED_TIMING, retryTestOptions) + + when: + HttpResponse response = retryTestFactory.send(retryTestURL).block() + + then: + response.statusCode() == 200 + retryTestFactory.getTryNumber() == 4 + } + + def "Retries non replyable flux"() { + setup: + RequestRetryTestFactory retryTestFactory = new RequestRetryTestFactory(RequestRetryTestFactory.RETRY_TEST_SCENARIO_NON_REPLAYABLE_FLOWABLE, retryTestOptions) + + when: + retryTestFactory.send(retryTestURL).block() + + then: + def e = thrown(IllegalStateException) + e.getMessage().startsWith("The request failed because") + e.getCause() instanceof UnexpectedLengthException + } + + @Unroll + def "Retries options invalid"() { + when: + new RequestRetryOptions(null, maxTries, tryTimeout, retryDelayInMs, maxRetryDelayInMs, null) + + then: + thrown(IllegalArgumentException) + + where: + maxTries | tryTimeout | retryDelayInMs | maxRetryDelayInMs + 0 | null | null | null + null | 0 | null | null + null | null | 0 | 1 + null | null | 1 | 0 + null | null | null | 1 + null | null | 1 | null + null | null | 5 | 4 + } +} From 2f25b625c309dd4ee05bd366b5bb28e3f0296a32 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Mon, 15 Jul 2019 16:44:57 -0700 Subject: [PATCH 08/19] Passing most of HelperTest --- .../storage/blob/AccountSASPermission.java | 2 +- .../storage/blob/AccountSASResourceType.java | 2 +- .../blob/AccountSASSignatureValues.java | 2 +- .../azure/storage/blob/BlobSASPermission.java | 2 +- .../storage/blob/ContainerSASPermission.java | 2 +- .../storage/blob/SASQueryParameters.java | 6 +- .../azure/storage/blob/models/BlobRange.java | 7 +- .../com/azure/storage/blob/HelperTest.groovy | 954 ++++++++++++++++++ 8 files changed, 964 insertions(+), 13 deletions(-) create mode 100644 storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASPermission.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASPermission.java index 11369aabed0d7..87ef5998c7252 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASPermission.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASPermission.java @@ -33,7 +33,7 @@ final class AccountSASPermission { /** * Initializes an {@code AccountSASPermission} object with all fields set to false. */ - private AccountSASPermission() { + public AccountSASPermission() { } /** diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASResourceType.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASResourceType.java index 6fc53c1902937..a1b47dc4f7372 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASResourceType.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASResourceType.java @@ -23,7 +23,7 @@ final class AccountSASResourceType { /** * Initializes an {@code AccountSASResourceType} object with all fields set to false. */ - private AccountSASResourceType() { + public AccountSASResourceType() { } /** diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASSignatureValues.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASSignatureValues.java index d60118d87b121..c8c3e0e58a78b 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASSignatureValues.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASSignatureValues.java @@ -50,7 +50,7 @@ final class AccountSASSignatureValues { * Initializes an {@code AccountSASSignatureValues} object with the version number set to the default and all * other values empty. */ - AccountSASSignatureValues() { + public AccountSASSignatureValues() { } /** diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobSASPermission.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobSASPermission.java index 6dd79f092ecd3..087376a70904a 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobSASPermission.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobSASPermission.java @@ -27,7 +27,7 @@ final class BlobSASPermission { /** * Initializes a {@code BlobSASPermission} object with all fields set to false. */ - private BlobSASPermission() { + BlobSASPermission() { } /** diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerSASPermission.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerSASPermission.java index 0ac5e0f6738a3..e7be8f0d39a39 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerSASPermission.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerSASPermission.java @@ -29,7 +29,7 @@ final class ContainerSASPermission { /** * Initializes an {@code ContainerSASPermssion} object with all fields set to false. */ - private ContainerSASPermission() { + ContainerSASPermission() { } /** diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/SASQueryParameters.java b/storage/client/blob/src/main/java/com/azure/storage/blob/SASQueryParameters.java index b6afd82addcab..f5b70a957baf6 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/SASQueryParameters.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/SASQueryParameters.java @@ -71,7 +71,7 @@ final class SASQueryParameters { * @param queryParamsMap All query parameters for the request as key-value pairs * @param removeSASParametersFromMap When {@code true}, the SAS query parameters will be removed from queryParamsMap */ - SASQueryParameters(Map queryParamsMap, boolean removeSASParametersFromMap) { + public SASQueryParameters(Map queryParamsMap, boolean removeSASParametersFromMap) { this.version = getQueryParameter(queryParamsMap, Constants.UrlConstants.SAS_SERVICE_VERSION, removeSASParametersFromMap); this.services = getQueryParameter(queryParamsMap, Constants.UrlConstants.SAS_SERVICES, removeSASParametersFromMap); this.resourceTypes = getQueryParameter(queryParamsMap, Constants.UrlConstants.SAS_RESOURCES_TYPES, removeSASParametersFromMap); @@ -333,9 +333,7 @@ UserDelegationKey userDelegationKey() { private void tryAppendQueryParameter(StringBuilder sb, String param, Object value) { if (value != null) { - if (sb.length() == 0) { - sb.append('?'); - } else { + if (sb.length() != 0) { sb.append('&'); } sb.append(safeURLEncode(param)).append('=').append(safeURLEncode(value.toString())); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/models/BlobRange.java b/storage/client/blob/src/main/java/com/azure/storage/blob/models/BlobRange.java index 183ebbf5664ad..f39278c25512d 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/models/BlobRange.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/models/BlobRange.java @@ -40,11 +40,10 @@ public BlobRange(long offset) { * @param count * the number of bytes to download */ - public BlobRange(long offset, long count) { + public BlobRange(long offset, Long count) { this(offset); - if (count < 0) { - throw new IllegalArgumentException( - "BlobRange count must be greater than or equal to 0 if specified."); + if (count != null && count < 0) { + throw new IllegalArgumentException("BlobRange count must be greater than or equal to 0 if specified."); } this.count = count; } diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy new file mode 100644 index 0000000000000..135190410fea0 --- /dev/null +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy @@ -0,0 +1,954 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob + +import com.azure.core.http.rest.Response +import com.azure.core.http.rest.VoidResponse +import com.azure.storage.blob.models.AccessPolicy +import com.azure.storage.blob.models.BlobRange +import com.azure.storage.blob.models.SignedIdentifier +import com.azure.storage.blob.models.UserDelegationKey +import com.azure.storage.common.credentials.SASTokenCredential +import spock.lang.Unroll + +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset + +class HelperTest extends APISpec { + + // TODO (alzimmer): Turn this on when nextPageLink can be passed into listing + /*def "responseError"() { + when: + cu.listBlobsFlat().iterator().hasNext() + + then: + def e = thrown(StorageException) + e.errorCode() == StorageErrorCode.INVALID_QUERY_PARAMETER_VALUE + e.statusCode() == 400 + e.message().contains("Value for one of the query parameters specified in the request URI is invalid.") + e.getMessage().contains(" properties = bu.getProperties() + + then: + properties.value().cacheControl() == "cache" + properties.value().contentDisposition() == "disposition" + properties.value().contentEncoding() == "encoding" + properties.value().contentLanguage() == "language" + properties.headers().value("Content-Type") == "type" + } + + def "serviceSASSignatureValues network test blob snapshot"() { + setup: + String containerName = generateContainerName() + String blobName = generateBlobName() + ContainerClient cu = primaryServiceURL.createContainer(containerName).value() + BlockBlobClient bu = cu.getBlockBlobClient(blobName) + bu.upload(defaultInputStream.get(), defaultDataSize) // need something to snapshot + String snapshotId = bu.createSnapshot().value() + + BlobSASPermission p = new BlobSASPermission() + .read(true) + .write(true) + .create(true) + .delete(true) + .add(true) + + IPRange ipR = new IPRange() + .ipMin("0.0.0.0") + .ipMax("255.255.255.255") + + ServiceSASSignatureValues v = new ServiceSASSignatureValues() + .permissions(p.toString()) + .startTime(OffsetDateTime.now().minusDays(1)) + .expiryTime(OffsetDateTime.now().plusDays(1)) + .containerName(containerName) + .blobName(blobName) + .snapshotId(snapshotId) + .ipRange(ipR) + .protocol(SASProtocol.HTTPS_ONLY) + .cacheControl("cache") + .contentDisposition("disposition") + .contentEncoding("encoding") + .contentLanguage("language") + .contentType("type") + + when: + BlobURLParts parts = URLParser.parse(bu.getBlobUrl()) + parts.sasQueryParameters(v.generateSASQueryParameters(primaryCreds)).scheme("https") + // base blob with snapshot SAS + AppendBlobClient bsu = new AppendBlobClientBuilder() + .endpoint(parts.toURL().toString()) + .buildClient() + + bsu.download(new ByteArrayOutputStream()) + + then: + // snapshot-level SAS shouldn't be able to access base blob + def e = thrown(StorageException) + + when: + // blob snapshot with snapshot SAS + parts.snapshot(snapshotId) + bsu = new AppendBlobClientBuilder() + .endpoint(parts.toURL().toString()) + .credential(new SASTokenCredential()) + .buildClient() + + ByteArrayOutputStream data = new ByteArrayOutputStream() + bsu.download(data) + + then: + notThrown(StorageException) + data.toByteArray() == defaultData.array() + + and: + Response properties = bsu.getProperties() + + then: + properties.value().cacheControl() == "cache" + properties.value().contentDisposition() == "disposition" + properties.value().contentEncoding() == "encoding" + properties.value().contentLanguage() == "language" + properties.headers().value("Content-Type") == "type" + } + + def "serviceSASSignatureValues network test container"() { + setup: + String containerName = generateContainerName() + ContainerClient cu = primaryServiceURL.createContainer(containerName).value() + SignedIdentifier id = new SignedIdentifier() + .id("0000") + .accessPolicy(new AccessPolicy().permission("racwdl") + .expiry(OffsetDateTime.now().plusDays(1))) + cu.setAccessPolicy(null, Arrays.asList(id)) + + // Check id field + ServiceSASSignatureValues v = new ServiceSASSignatureValues() + .identifier("0000") + .containerName(containerName) + .protocol(SASProtocol.HTTPS_ONLY) + + // Check containerSASPermissions + ContainerSASPermission p = new ContainerSASPermission() + .read(true) + .write(true) + .create(true) + .delete(true) + .add(true) + .list(true) + ServiceSASSignatureValues v2 = new ServiceSASSignatureValues() + .permissions(p.toString()) + .expiryTime(OffsetDateTime.now().plusDays(1)) + .containerName(containerName) + + when: + BlobURLParts parts = URLParser.parse(cu.getContainerUrl()) + .sasQueryParameters(v.generateSASQueryParameters(primaryCreds)) + .scheme("https") + ContainerClient cuSAS = new ContainerClientBuilder() + .endpoint(parts.toURL().toString()) + .anonymousCredential() + .buildClient() + + parts.sasQueryParameters(v2.generateSASQueryParameters(primaryCreds)) + ContainerClient cuSAS2 = new ContainerClientBuilder() + .endpoint(parts.toURL().toString()) + .anonymousCredential() + .buildClient() + + then: + cuSAS.getProperties() + cuSAS2.getProperties() + notThrown(StorageException) + } + + def "serviceSASSignatureValues network test blob user delegation"() { + setup: + String containerName = generateContainerName() + String blobName = generateBlobName() + ContainerClient cu = primaryServiceURL.createContainer(containerName).value() + + BlobSASPermission p = new BlobSASPermission() + .read(true) + .write(true) + .create(true) + .delete(true) + .add(true) + + IPRange ipR = new IPRange() + .ipMin("0.0.0.0") + .ipMax("255.255.255.255") + + ServiceSASSignatureValues v = new ServiceSASSignatureValues() + .permissions(p.toString()) + .startTime(OffsetDateTime.now().minusDays(1)) + .expiryTime(OffsetDateTime.now().plusDays(1)) + .containerName(containerName) + .blobName(blobName) + .ipRange(ipR) + .protocol(SASProtocol.HTTPS_ONLY) + .cacheControl("cache") + .contentDisposition("disposition") + .contentEncoding("encoding") + .contentLanguage("language") + .contentType("type") + + UserDelegationKey key = getOAuthServiceURL().getUserDelegationKey(null, OffsetDateTime.now().plusDays(1)).value() + + when: + BlobURLParts parts = URLParser.parse(cu.getBlobClient(blobName).getBlobUrl()) + parts.sasQueryParameters(v.generateSASQueryParameters(key, primaryCreds.accountName())).scheme("https") + AppendBlobClient bu = new AppendBlobClientBuilder() + .endpoint(parts.toURL().toString()) + .anonymousCredential() + .buildClient() + + then: + bu.create() + + and: + Response properties = bu.getProperties() + + then: + properties.value().cacheControl() == "cache" + properties.value().contentDisposition() == "disposition" + properties.value().contentEncoding() == "encoding" + properties.value().contentLanguage() == "language" + properties.headers().value("Content-Type") == "type" + } + + def "serviceSASSignatureValues network test blob snapshot user delegation"() { + setup: + String containerName = generateContainerName() + String blobName = generateBlobName() + ContainerClient cu = primaryServiceURL.createContainer(containerName).value() + BlockBlobClient bu = cu.getBlockBlobClient(blobName) + bu.upload(defaultInputStream.get(), defaultDataSize) // need something to snapshot + String snapshotId = bu.createSnapshot().value() + + BlobSASPermission p = new BlobSASPermission() + .read(true) + .write(true) + .create(true) + .delete(true) + .add(true) + + def ipR = new IPRange() + .ipMin("0.0.0.0") + .ipMax("255.255.255.255") + + ServiceSASSignatureValues v = new ServiceSASSignatureValues() + .permissions(p.toString()) + .startTime(OffsetDateTime.now().minusDays(1)) + .expiryTime(OffsetDateTime.now().plusDays(1)) + .containerName(containerName) + .blobName(blobName) + .snapshotId(snapshotId) + .ipRange(ipR) + .protocol(SASProtocol.HTTPS_ONLY) + .cacheControl("cache") + .contentDisposition("disposition") + .contentEncoding("encoding") + .contentLanguage("language") + .contentType("type") + + UserDelegationKey key = getOAuthServiceURL().getUserDelegationKey(null, OffsetDateTime.now().plusDays(1)).value() + + when: + BlobURLParts parts = URLParser.parse(bu.getBlobUrl()) + parts.sasQueryParameters(v.generateSASQueryParameters(key, primaryCreds.accountName())).scheme("https") + // base blob with snapshot SAS + AppendBlobClient bsu = new AppendBlobClientBuilder() + .endpoint(parts.toURL().toString()) + .anonymousCredential() + .buildClient() + bsu.download(new ByteArrayOutputStream()) + + then: + // snapshot-level SAS shouldn't be able to access base blob + thrown(StorageException) + + when: + // blob snapshot with snapshot SAS + parts.snapshot(snapshotId) + bsu = new AppendBlobClientBuilder() + .endpoint(parts.toURL().toString()) + .anonymousCredential() + .buildClient() + + ByteArrayOutputStream data = new ByteArrayOutputStream() + bsu.download(data) + + then: + notThrown(StorageException) + data.toByteArray() == defaultData.array() + + and: + Response properties = bsu.getProperties() + + then: + properties.value().cacheControl() == "cache" + properties.value().contentDisposition() == "disposition" + properties.value().contentEncoding() == "encoding" + properties.value().contentLanguage() == "language" + properties.headers().value("Content-Type") == "type" + } + + def "serviceSASSignatureValues network test container user delegation"() { + setup: + String containerName = generateContainerName() + ContainerClient cu = primaryServiceURL.createContainer(containerName).value() + + ContainerSASPermission p = new ContainerSASPermission() + .read(true) + .write(true) + .create(true) + .delete(true) + .add(true) + .list(true) + ServiceSASSignatureValues v = new ServiceSASSignatureValues() + .containerName(containerName) + .protocol(SASProtocol.HTTPS_HTTP) + .expiryTime(OffsetDateTime.now().plusHours(5)) + .permissions(p.toString()) + + UserDelegationKey key = getOAuthServiceURL().getUserDelegationKey(null, OffsetDateTime.now().plusDays(1)).value() + + when: + BlobURLParts parts = URLParser.parse(cu.getContainerUrl()) + .sasQueryParameters(v.generateSASQueryParameters(key, primaryCreds.accountName())) + .scheme("http") + ContainerClient cuSAS = new ContainerClientBuilder() + .endpoint(parts.toURL().toString()) + .anonymousCredential() + .buildClient() + + then: + cuSAS.getProperties() + notThrown(StorageException) + } + + /* + This test will ensure that each field gets placed into the proper location within the string to sign and that null + values are handled correctly. We will validate the whole SAS with service calls as well as correct serialization of + individual parts later. + */ + + @Unroll + def "serviceSasSignatures string to sign"() { + when: + ServiceSASSignatureValues v = new ServiceSASSignatureValues() + if (permissions != null) { + v.permissions(new BlobSASPermission().read(true).toString()) + } + v.startTime(startTime) + .expiryTime(expiryTime) + .containerName("containerName") + .blobName("blobName") + .snapshotId(snapId) + + if (ipRange != null) { + v.ipRange(new IPRange().ipMin("ip")) + } + + v.identifier(identifier) + .protocol(protocol) + .cacheControl(cacheControl) + .contentDisposition(disposition) + .contentEncoding(encoding) + .contentLanguage(language) + .contentType(type) + + SASQueryParameters token = v.generateSASQueryParameters(primaryCreds) + + then: + token.signature() == primaryCreds.computeHmac256(expectedStringToSign) + + /* + We don't test the blob or containerName properties because canonicalized resource is always added as at least + /blob/accountName. We test canonicalization of resources later. Again, this is not to test a fully functional + sas but the construction of the string to sign. + Signed resource is tested elsewhere, as we work some minor magic in choosing which value to use. + */ + where: + permissions | startTime | expiryTime | identifier | ipRange | protocol | snapId | cacheControl | disposition | encoding | language | type || expectedStringToSign + new BlobSASPermission() | null | null | null | null | null | null | null | null | null | null | null || "r\n\n\n" + "/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null | null | null | null | null | null | null | null | null || "\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null | null | null | null | null | null | null | null || "\n\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | "id" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\nid\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | new IPRange() | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\nip\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | SASProtocol.HTTPS_ONLY | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n" + SASProtocol.HTTPS_ONLY + "\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | "snapId" | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nsnapId\n\n\n\n\n" + null | null | null | null | null | null | null | "control" | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\ncontrol\n\n\n\n" + null | null | null | null | null | null | null | null | "disposition" | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\ndisposition\n\n\n" + null | null | null | null | null | null | null | null | null | "encoding" | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\nencoding\n\n" + null | null | null | null | null | null | null | null | null | null | "language" | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\nlanguage\n" + null | null | null | null | null | null | null | null | null | null | null | "type" || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\ntype" + } + + @Unroll + def "serviceSasSignatures string to sign user delegation key"() { + when: + ServiceSASSignatureValues v = new ServiceSASSignatureValues() + if (permissions != null) { + v.permissions(new BlobSASPermission().read(true).toString()) + } + + v.startTime(startTime) + .expiryTime(expiryTime) + .containerName("containerName") + .blobName("blobName") + .snapshotId(snapId) + + if (ipRange != null) { + v.ipRange(new IPRange().ipMin("ip")) + } + + v.protocol(protocol) + .cacheControl(cacheControl) + .contentDisposition(disposition) + .contentEncoding(encoding) + .contentLanguage(language) + .contentType(type) + + UserDelegationKey key = new UserDelegationKey() + .signedOid(keyOid) + .signedTid(keyTid) + .signedStart(keyStart) + .signedExpiry(keyExpiry) + .signedService(keyService) + .signedVersion(keyVersion) + .value(keyValue) + + SASQueryParameters token = v.generateSASQueryParameters(key, primaryCreds.accountName()) + + then: + token.signature() == Utility.delegateComputeHmac256(key, expectedStringToSign) + + /* + We test string to sign functionality directly related to user delegation sas specific parameters + */ + where: + permissions | startTime | expiryTime | keyOid | keyTid | keyStart | keyExpiry | keyService | keyVersion | keyValue | ipRange | protocol | snapId | cacheControl | disposition | encoding | language | type || expectedStringToSign + new BlobSASPermission() | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "r\n\n\n" + "/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | "11111111-1111-1111-1111-111111111111" | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n11111111-1111-1111-1111-111111111111\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | "22222222-2222-2222-2222-222222222222" | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n22222222-2222-2222-2222-222222222222\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC) | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n2018-01-01T00:00:00Z\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC) | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n2018-01-01T00:00:00Z\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | null | "b" | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\nb\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | null | null | "2018-06-17" | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n2018-06-17\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | new IPRange() | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\nip\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | SASProtocol.HTTPS_ONLY | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n" + SASProtocol.HTTPS_ONLY + "\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | "snapId" | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nsnapId\n\n\n\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | "control" | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\ncontrol\n\n\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | "disposition" | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\ndisposition\n\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | "encoding" | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\nencoding\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | "language" | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\nlanguage\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | "type" || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\ntype" + } + + @Unroll + def "serviceSASSignatureValues canonicalizedResource"() { + setup: + ServiceSASSignatureValues v = new ServiceSASSignatureValues() + .containerName(containerName) + .blobName(blobName) + .snapshotId(snapId) + + when: + SASQueryParameters token = v.generateSASQueryParameters(primaryCreds) + + then: + token.signature() == primaryCreds.computeHmac256(expectedStringToSign) + token.resource() == expectedResource + + where: + containerName | blobName | snapId || expectedResource | expectedStringToSign + "c" | "b" | "id" || "bs" | "\n\n\n" + "/blob/" + primaryCreds.accountName() + "/c/b\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nid\n\n\n\n\n" + "c" | "b" | null || "b" | "\n\n\n" + "/blob/" + primaryCreds.accountName() + "/c/b\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + "c" | null | null || "c" | "\n\n\n" + "/blob/" + primaryCreds.accountName() + "/c\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nc\n\n\n\n\n\n" + + } + + @Unroll + def "serviceSasSignatureValues IA"() { + setup: + ServiceSASSignatureValues v = new ServiceSASSignatureValues() + .containerName(containerName) + .blobName(blobName) + .snapshotId("2018-01-01T00:00:00.0000000Z") + .version(version) + + when: + v.generateSASQueryParameters(creds) + + then: + def e = thrown(IllegalArgumentException) + e.getMessage().contains(parameter) + + where: + containerName | version | creds | blobName || parameter + null | "v" | primaryCreds | "b" | "container" + "c" | null | primaryCreds | "b" | "version" + "c" | "v" | null | "b" | "sharedKeyCredentials" + "c" | "v" | primaryCreds | null | "blobName" + } + + @Unroll + def "BlobSASPermissions toString"() { + setup: + BlobSASPermission perms = new BlobSASPermission() + .read(read) + .write(write) + .delete(delete) + .create(create) + .add(add) + + expect: + perms.toString() == expectedString + + where: + read | write | delete | create | add || expectedString + true | false | false | false | false || "r" + false | true | false | false | false || "w" + false | false | true | false | false || "d" + false | false | false | true | false || "c" + false | false | false | false | true || "a" + true | true | true | true | true || "racwd" + } + + @Unroll + def "BlobSASPermissions parse"() { + when: + BlobSASPermission perms = BlobSASPermission.parse(permString) + + then: + perms.read() == read + perms.write() == write + perms.delete() == delete + perms.create() == create + perms.add() == add + + where: + permString || read | write | delete | create | add + "r" || true | false | false | false | false + "w" || false | true | false | false | false + "d" || false | false | true | false | false + "c" || false | false | false | true | false + "a" || false | false | false | false | true + "racwd" || true | true | true | true | true + "dcwra" || true | true | true | true | true + } + + def "BlobSASPermissions parse IA"() { + when: + BlobSASPermission.parse("rwaq") + + then: + thrown(IllegalArgumentException) + } + + @Unroll + def "ContainerSASPermissions toString"() { + setup: + ContainerSASPermission perms = new ContainerSASPermission() + .read(read) + .write(write) + .delete(delete) + .create(create) + .add(add) + .list(list) + + expect: + perms.toString() == expectedString + + where: + read | write | delete | create | add | list || expectedString + true | false | false | false | false | false || "r" + false | true | false | false | false | false || "w" + false | false | true | false | false | false || "d" + false | false | false | true | false | false || "c" + false | false | false | false | true | false || "a" + false | false | false | false | false | true || "l" + true | true | true | true | true | true || "racwdl" + } + + @Unroll + def "ContainerSASPermissions parse"() { + when: + ContainerSASPermission perms = ContainerSASPermission.parse(permString) + + then: + perms.read() == read + perms.write() == write + perms.delete() == delete + perms.create() == create + perms.add() == add + perms.list() == list + + where: + permString || read | write | delete | create | add | list + "r" || true | false | false | false | false | false + "w" || false | true | false | false | false | false + "d" || false | false | true | false | false | false + "c" || false | false | false | true | false | false + "a" || false | false | false | false | true | false + "l" || false | false | false | false | false | true + "racwdl" || true | true | true | true | true | true + "dcwrla" || true | true | true | true | true | true + } + + def "ContainerSASPermissions parse IA"() { + when: + ContainerSASPermission.parse("rwaq") + + then: + thrown(IllegalArgumentException) + } + + @Unroll + def "IPRange toString"() { + setup: + def ip = new IPRange() + .ipMin(min) + .ipMax(max) + + expect: + ip.toString() == expectedString + + where: + min | max || expectedString + "a" | "b" || "a-b" + "a" | null || "a" + null | "b" || "" + } + + @Unroll + def "IPRange parse"() { + when: + IPRange ip = IPRange.parse(rangeStr) + + then: + ip.ipMin() == min + ip.ipMax() == max + + where: + rangeStr || min | max + "a-b" || "a" | "b" + "a" || "a" | null + "" || "" | null + } + + @Unroll + def "SASProtocol parse"() { + expect: + SASProtocol.parse(protocolStr) == protocol + + where: + protocolStr || protocol + "https" || SASProtocol.HTTPS_ONLY + "https,http" || SASProtocol.HTTPS_HTTP + } + + /* + This test will ensure that each field gets placed into the proper location within the string to sign and that null + values are handled correctly. We will validate the whole SAS with service calls as well as correct serialization of + individual parts later. + */ + + @Unroll + def "accountSasSignatures string to sign"() { + when: + AccountSASSignatureValues v = new AccountSASSignatureValues() + .permissions(new AccountSASPermission().read(true).toString()) + .services("b") + .resourceTypes("o") + .startTime(startTime) + .expiryTime(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + .protocol(protocol) + + if (ipRange != null) { + v.ipRange(new IPRange().ipMin("ip")) + } + + def token = v.generateSASQueryParameters(primaryCreds) + + then: + token.signature() == primaryCreds.computeHmac256(expectedStringToSign) + + where: + startTime | ipRange | protocol || expectedStringToSign + OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null || primaryCreds.accountName() + "\nr\nb\no\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\n" + null | new IPRange() | null || primaryCreds.accountName() + "\nr\nb\no\n\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\nip\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\n" + null | null | SASProtocol.HTTPS_ONLY || primaryCreds.accountName() + "\nr\nb\no\n\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n\n" + SASProtocol.HTTPS_ONLY + "\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\n" + } + + @Unroll + def "accountSasSignatureValues IA"() { + setup: + AccountSASSignatureValues v = new AccountSASSignatureValues() + .permissions(permissions) + .services(service) + .resourceTypes(resourceType) + .expiryTime(expiryTime) + .version(version) + + when: + v.generateSASQueryParameters(creds) + + then: + def e = thrown(IllegalArgumentException) + e.getMessage().contains(parameter) + + where: + permissions | service | resourceType | expiryTime | version | creds || parameter + null | "b" | "c" | OffsetDateTime.now() | "v" | primaryCreds || "permissions" + "c" | null | "c" | OffsetDateTime.now() | "v" | primaryCreds || "services" + "c" | "b" | null | OffsetDateTime.now() | "v" | primaryCreds || "resourceTypes" + "c" | "b" | "c" | null | "v" | primaryCreds || "expiryTime" + "c" | "b" | "c" | OffsetDateTime.now() | null | primaryCreds || "version" + "c" | "b" | "c" | OffsetDateTime.now() | "v" | null || "SharedKeyCredential" + } + + @Unroll + def "AccountSASPermissions toString"() { + setup: + AccountSASPermission perms = new AccountSASPermission() + .read(read) + .write(write) + .delete(delete) + .list(list) + .add(add) + .create(create) + .update(update) + .processMessages(process) + + expect: + perms.toString() == expectedString + + where: + read | write | delete | list | add | create | update | process || expectedString + true | false | false | false | false | false | false | false || "r" + false | true | false | false | false | false | false | false || "w" + false | false | true | false | false | false | false | false || "d" + false | false | false | true | false | false | false | false || "l" + false | false | false | false | true | false | false | false || "a" + false | false | false | false | false | true | false | false || "c" + false | false | false | false | false | false | true | false || "u" + false | false | false | false | false | false | false | true || "p" + true | true | true | true | true | true | true | true || "rwdlacup" + } + + @Unroll + def "AccountSASPermissions parse"() { + when: + AccountSASPermission perms = AccountSASPermission.parse(permString) + + then: + perms.read() == read + perms.write() == write + perms.delete() == delete + perms.list() == list + perms.add() == add + perms.create() == create + perms.update() == update + perms.processMessages() == process + + where: + permString || read | write | delete | list | add | create | update | process + "r" || true | false | false | false | false | false | false | false + "w" || false | true | false | false | false | false | false | false + "d" || false | false | true | false | false | false | false | false + "l" || false | false | false | true | false | false | false | false + "a" || false | false | false | false | true | false | false | false + "c" || false | false | false | false | false | true | false | false + "u" || false | false | false | false | false | false | true | false + "p" || false | false | false | false | false | false | false | true + "rwdlacup" || true | true | true | true | true | true | true | true + "lwrupcad" || true | true | true | true | true | true | true | true + } + + def "AccountSASPermissions parse IA"() { + when: + AccountSASPermission.parse("rwaq") + + then: + thrown(IllegalArgumentException) + } + + @Unroll + def "AccountSASResourceType toString"() { + setup: + AccountSASResourceType resourceTypes = new AccountSASResourceType() + .service(service) + .container(container) + .object(object) + + expect: + resourceTypes.toString() == expectedString + + where: + service | container | object || expectedString + true | false | false || "s" + false | true | false || "c" + false | false | true || "o" + true | true | true || "sco" + } + + @Unroll + def "AccountSASResourceType parse"() { + when: + AccountSASResourceType resourceTypes = AccountSASResourceType.parse(resourceTypeString) + + then: + resourceTypes.service() == service + resourceTypes.container() == container + resourceTypes.object() == object + + where: + resourceTypeString || service | container | object + "s" || true | false | false + "c" || false | true | false + "o" || false | false | true + "sco" || true | true | true + } + + @Unroll + def "AccountSASResourceType IA"() { + when: + AccountSASResourceType.parse("scq") + + then: + thrown(IllegalArgumentException) + } + + def "BlobURLParts"() { + setup: + BlobURLParts parts = new BlobURLParts() + .scheme("http") + .host("host") + .containerName("container") + .blobName("blob") + .snapshot("snapshot") + + ServiceSASSignatureValues sasValues = new ServiceSASSignatureValues() + .permissions("r") + .containerName("container") + + parts.sasQueryParameters(sasValues.generateSASQueryParameters(primaryCreds)) + + when: + String[] splitParts = parts.toURL().toString().split("\\?") + + then: + splitParts.size() == 2 // Ensure that there is only one question mark even when sas and snapshot are present + splitParts[0] == "http://host/container/blob" + splitParts[1].contains("snapshot=snapshot") + splitParts[1].contains("sp=r") + splitParts[1].contains("sig=") + splitParts[1].split("&").size() == 5 // snapshot & sv & sr & sp & sig + } + + def "URLParser"() { + when: + BlobURLParts parts = URLParser.parse(new URL("http://host/container/blob?snapshot=snapshot&sv=" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "&sr=c&sp=r&sig=Ee%2BSodSXamKSzivSdRTqYGh7AeMVEk3wEoRZ1yzkpSc%3D")) + + then: + parts.scheme() == "http" + parts.host() == "host" + parts.containerName() == "container" + parts.blobName() == "blob" + parts.snapshot() == "snapshot" + parts.sasQueryParameters().permissions() == "r" + parts.sasQueryParameters().version() == Constants.HeaderConstants.TARGET_STORAGE_VERSION + parts.sasQueryParameters().resource() == "c" + parts.sasQueryParameters().signature() == Utility.safeURLDecode("Ee%2BSodSXamKSzivSdRTqYGh7AeMVEk3wEoRZ1yzkpSc%3D") + } +} From b8d75af46fbc6169e135ce56b1c2627f31ae043c Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Tue, 16 Jul 2019 09:23:56 -0700 Subject: [PATCH 09/19] Fixing tests after merge --- .../azure/storage/blob/AccountSASService.java | 2 +- .../azure/storage/blob/BlobAsyncClient.java | 8 +-- .../azure/storage/blob/BlobSASPermission.java | 2 +- .../blob/ServiceSASSignatureValues.java | 20 ++++-- .../storage/blob/StorageAsyncClient.java | 2 +- .../com/azure/storage/blob/HelperTest.groovy | 67 +++++++++++-------- 6 files changed, 61 insertions(+), 40 deletions(-) diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASService.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASService.java index 0f88ccb2203a9..359085fb13084 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASService.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASService.java @@ -25,7 +25,7 @@ final class AccountSASService { /** * Initializes an {@code AccountSASService} object with all fields set to false. */ - private AccountSASService() { + public AccountSASService() { } /** diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java index ec35f846f2bad..014b98d601a85 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java @@ -1096,7 +1096,7 @@ public String generateSAS(String identifier, BlobSASPermission permissions, Offs cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); SharedKeyCredential sharedKeyCredential = - Utility.getSharedKeyCredential(this.blobAsyncRawClient.azureBlobStorage.httpPipeline()); + Utility.getSharedKeyCredential(this.azureBlobStorage.httpPipeline()); Utility.assertNotNull("sharedKeyCredential", sharedKeyCredential); @@ -1115,7 +1115,7 @@ ServiceSASSignatureValues configureServiceSASSignatureValues(ServiceSASSignature String accountName) { // Set canonical name - serviceSASSignatureValues.canonicalName(this.blobAsyncRawClient.azureBlobStorage.url(), accountName); + serviceSASSignatureValues.canonicalName(this.azureBlobStorage.url(), accountName); // Set snapshotId serviceSASSignatureValues.snapshotId(getSnapshotId()); @@ -1137,7 +1137,7 @@ ServiceSASSignatureValues configureServiceSASSignatureValues(ServiceSASSignature * A string that represents the snapshotId of the snapshot blob */ public String getSnapshotId() { - return this.blobAsyncRawClient.snapshot; + return this.snapshot; } /** @@ -1147,6 +1147,6 @@ public String getSnapshotId() { * A boolean that indicates if a blob is a snapshot */ public boolean isSnapshot() { - return this.blobAsyncRawClient.snapshot != null; + return this.snapshot != null; } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobSASPermission.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobSASPermission.java index 087376a70904a..6c8843a140bd8 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobSASPermission.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobSASPermission.java @@ -27,7 +27,7 @@ final class BlobSASPermission { /** * Initializes a {@code BlobSASPermission} object with all fields set to false. */ - BlobSASPermission() { + public BlobSASPermission() { } /** diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java index 114c28eb71209..8abba57f65a12 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java @@ -372,7 +372,7 @@ public SASQueryParameters generateSASQueryParameters(SharedKeyCredential sharedK // Signature is generated on the un-url-encoded values. final String stringToSign = stringToSign(); - String signature = null; + String signature; try { signature = sharedKeyCredentials.computeHmac256(stringToSign); } catch (InvalidKeyException e) { @@ -380,7 +380,7 @@ public SASQueryParameters generateSASQueryParameters(SharedKeyCredential sharedK } return new SASQueryParameters(this.version, null, null, - this.protocol, this.startTime, this.expiryTime, this.ipRange, this.identifier, resource, + this.protocol, this.startTime, this.expiryTime, this.ipRange, this.identifier, this.resource, this.permissions, signature, this.cacheControl, this.contentDisposition, this.contentEncoding, this.contentLanguage, this.contentType, null /* delegate */); } @@ -401,7 +401,7 @@ public SASQueryParameters generateSASQueryParameters(UserDelegationKey delegatio // Signature is generated on the un-url-encoded values. final String stringToSign = stringToSign(delegationKey); - String signature = null; + String signature; try { signature = Utility.delegateComputeHmac256(delegationKey, stringToSign); } catch (InvalidKeyException e) { @@ -422,7 +422,7 @@ private void assertGenerateOK(boolean usingUserDelegation) { Utility.assertNotNull("canonicalName", this.canonicalName); // Ensure either (expiryTime and permissions) or (identifier) is set - if (this.expiryTime == null || this.permissions == null) { + if (this.identifier == null && (this.expiryTime == null || this.permissions == null)) { // Identifier is not required if user delegation is being used if (!usingUserDelegation) { Utility.assertNotNull("identifier", this.identifier); @@ -449,7 +449,7 @@ private String stringToSign() { this.ipRange == null ? (new IPRange()).toString() : this.ipRange.toString(), this.protocol == null ? "" : protocol.toString(), this.version == null ? "" : this.version, - this.resource == null ? "" : this.resource, + this.resource == null ? "" : getResource(), this.snapshotId == null ? "" : this.snapshotId, this.cacheControl == null ? "" : this.cacheControl, this.contentDisposition == null ? "" : this.contentDisposition, @@ -474,7 +474,7 @@ private String stringToSign(final UserDelegationKey key) { this.ipRange == null ? new IPRange().toString() : this.ipRange.toString(), this.protocol == null ? "" : this.protocol.toString(), this.version == null ? "" : this.version, - this.resource == null ? "" : this.resource, + this.resource == null ? "" : getResource(), this.snapshotId == null ? "" : this.snapshotId, this.cacheControl == null ? "" : this.cacheControl, this.contentDisposition == null ? "" : this.contentDisposition, @@ -483,4 +483,12 @@ private String stringToSign(final UserDelegationKey key) { this.contentType == null ? "" : this.contentType ); } + + private String getResource() { + if (Constants.UrlConstants.SAS_BLOB_CONSTANT.equals(resource) && this.snapshotId != null) { + return Constants.UrlConstants.SAS_BLOB_SNAPSHOT_CONSTANT; + } else { + return resource; + } + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java index f6f383a62e664..2f8e0ac160836 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java @@ -339,7 +339,7 @@ public String generateAccountSAS(AccountSASService accountSASService, AccountSAS accountSASSignatureValues.ipRange(ipRange); accountSASSignatureValues.protocol(sasProtocol); - SharedKeyCredential sharedKeyCredential = Utility.getSharedKeyCredential(this.storageAsyncRawClient.azureBlobStorage.httpPipeline()); + SharedKeyCredential sharedKeyCredential = Utility.getSharedKeyCredential(this.azureBlobStorage.httpPipeline()); SASQueryParameters sasQueryParameters = accountSASSignatureValues.generateSASQueryParameters(sharedKeyCredential); diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy index 135190410fea0..9d8bd861418e5 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy @@ -10,6 +10,7 @@ import com.azure.storage.blob.models.BlobRange import com.azure.storage.blob.models.SignedIdentifier import com.azure.storage.blob.models.UserDelegationKey import com.azure.storage.common.credentials.SASTokenCredential +import com.azure.storage.common.credentials.SharedKeyCredential import spock.lang.Unroll import java.time.LocalDateTime @@ -90,8 +91,8 @@ class HelperTest extends APISpec { .permissions(p.toString()) .startTime(OffsetDateTime.now().minusDays(1)) .expiryTime(OffsetDateTime.now().plusDays(1)) - .containerName(containerName) - .blobName(blobName) + .resource(Constants.UrlConstants.SAS_BLOB_CONSTANT) + .canonicalName(String.format("/blob/%s/%s/%s", primaryCreds.accountName(), containerName, blobName)) .ipRange(ipR) .protocol(SASProtocol.HTTPS_ONLY) .cacheControl("cache") @@ -146,8 +147,8 @@ class HelperTest extends APISpec { .permissions(p.toString()) .startTime(OffsetDateTime.now().minusDays(1)) .expiryTime(OffsetDateTime.now().plusDays(1)) - .containerName(containerName) - .blobName(blobName) + .resource(Constants.UrlConstants.SAS_BLOB_SNAPSHOT_CONSTANT) + .canonicalName(String.format("/blob/%s/%s/%s", primaryCreds.accountName(), containerName, blobName)) .snapshotId(snapshotId) .ipRange(ipR) .protocol(SASProtocol.HTTPS_ONLY) @@ -169,7 +170,7 @@ class HelperTest extends APISpec { then: // snapshot-level SAS shouldn't be able to access base blob - def e = thrown(StorageException) + thrown(StorageException) when: // blob snapshot with snapshot SAS @@ -210,7 +211,8 @@ class HelperTest extends APISpec { // Check id field ServiceSASSignatureValues v = new ServiceSASSignatureValues() .identifier("0000") - .containerName(containerName) + .resource(Constants.UrlConstants.SAS_CONTAINER_CONSTANT) + .canonicalName(String.format("/blob/%s/%s", primaryCreds.accountName(), containerName)) .protocol(SASProtocol.HTTPS_ONLY) // Check containerSASPermissions @@ -224,7 +226,7 @@ class HelperTest extends APISpec { ServiceSASSignatureValues v2 = new ServiceSASSignatureValues() .permissions(p.toString()) .expiryTime(OffsetDateTime.now().plusDays(1)) - .containerName(containerName) + .resource(containerName) when: BlobURLParts parts = URLParser.parse(cu.getContainerUrl()) @@ -268,8 +270,8 @@ class HelperTest extends APISpec { .permissions(p.toString()) .startTime(OffsetDateTime.now().minusDays(1)) .expiryTime(OffsetDateTime.now().plusDays(1)) - .containerName(containerName) - .blobName(blobName) + .resource(Constants.UrlConstants.SAS_BLOB_CONSTANT) + .canonicalName(String.format("/blob/%s/%s/%s", primaryCreds.accountName(), containerName, blobName)) .ipRange(ipR) .protocol(SASProtocol.HTTPS_ONLY) .cacheControl("cache") @@ -282,7 +284,7 @@ class HelperTest extends APISpec { when: BlobURLParts parts = URLParser.parse(cu.getBlobClient(blobName).getBlobUrl()) - parts.sasQueryParameters(v.generateSASQueryParameters(key, primaryCreds.accountName())).scheme("https") + parts.sasQueryParameters(v.generateSASQueryParameters(key)).scheme("https") AppendBlobClient bu = new AppendBlobClientBuilder() .endpoint(parts.toURL().toString()) .anonymousCredential() @@ -326,8 +328,8 @@ class HelperTest extends APISpec { .permissions(p.toString()) .startTime(OffsetDateTime.now().minusDays(1)) .expiryTime(OffsetDateTime.now().plusDays(1)) - .containerName(containerName) - .blobName(blobName) + .resource(Constants.UrlConstants.SAS_BLOB_SNAPSHOT_CONSTANT) + .canonicalName(String.format("/blob/%s/%s/%s", primaryCreds.accountName(), containerName, blobName)) .snapshotId(snapshotId) .ipRange(ipR) .protocol(SASProtocol.HTTPS_ONLY) @@ -341,7 +343,7 @@ class HelperTest extends APISpec { when: BlobURLParts parts = URLParser.parse(bu.getBlobUrl()) - parts.sasQueryParameters(v.generateSASQueryParameters(key, primaryCreds.accountName())).scheme("https") + parts.sasQueryParameters(v.generateSASQueryParameters(key)).scheme("https") // base blob with snapshot SAS AppendBlobClient bsu = new AppendBlobClientBuilder() .endpoint(parts.toURL().toString()) @@ -391,8 +393,10 @@ class HelperTest extends APISpec { .delete(true) .add(true) .list(true) + ServiceSASSignatureValues v = new ServiceSASSignatureValues() - .containerName(containerName) + .resource(Constants.UrlConstants.SAS_CONTAINER_CONSTANT) + .canonicalName(String.format("/blob/%s/%s", primaryCreds.accountName(), containerName)) .protocol(SASProtocol.HTTPS_HTTP) .expiryTime(OffsetDateTime.now().plusHours(5)) .permissions(p.toString()) @@ -401,8 +405,9 @@ class HelperTest extends APISpec { when: BlobURLParts parts = URLParser.parse(cu.getContainerUrl()) - .sasQueryParameters(v.generateSASQueryParameters(key, primaryCreds.accountName())) + .sasQueryParameters(v.generateSASQueryParameters(key)) .scheme("http") + ContainerClient cuSAS = new ContainerClientBuilder() .endpoint(parts.toURL().toString()) .anonymousCredential() @@ -425,11 +430,14 @@ class HelperTest extends APISpec { ServiceSASSignatureValues v = new ServiceSASSignatureValues() if (permissions != null) { v.permissions(new BlobSASPermission().read(true).toString()) + } else { + v.permissions("") } + v.startTime(startTime) .expiryTime(expiryTime) - .containerName("containerName") - .blobName("blobName") + .resource(Constants.UrlConstants.SAS_BLOB_CONSTANT) + .canonicalName(String.format("/blob/%s/containerName/blobName", primaryCreds.accountName())) .snapshotId(snapId) if (ipRange != null) { @@ -481,8 +489,8 @@ class HelperTest extends APISpec { v.startTime(startTime) .expiryTime(expiryTime) - .containerName("containerName") - .blobName("blobName") + .resource(new AccountSASService().blob(true).toString()) + .canonicalName(String.format("/blob/%s/containerName/blobName", primaryCreds.accountName())) .snapshotId(snapId) if (ipRange != null) { @@ -505,7 +513,7 @@ class HelperTest extends APISpec { .signedVersion(keyVersion) .value(keyValue) - SASQueryParameters token = v.generateSASQueryParameters(key, primaryCreds.accountName()) + SASQueryParameters token = v.generateSASQueryParameters(key) then: token.signature() == Utility.delegateComputeHmac256(key, expectedStringToSign) @@ -538,8 +546,10 @@ class HelperTest extends APISpec { def "serviceSASSignatureValues canonicalizedResource"() { setup: ServiceSASSignatureValues v = new ServiceSASSignatureValues() - .containerName(containerName) - .blobName(blobName) + .expiryTime(OffsetDateTime.now()) + .permissions(new BlobSASPermission().toString()) + .resource(expectedResource) + .canonicalName(String.format("/blob/%s/%s/%s", primaryCreds.accountName(), containerName, blobName)) .snapshotId(snapId) when: @@ -561,13 +571,15 @@ class HelperTest extends APISpec { def "serviceSasSignatureValues IA"() { setup: ServiceSASSignatureValues v = new ServiceSASSignatureValues() - .containerName(containerName) - .blobName(blobName) + .permissions(new AccountSASPermission().toString()) + .expiryTime(OffsetDateTime.now()) + .resource(containerName) + .canonicalName(blobName) .snapshotId("2018-01-01T00:00:00.0000000Z") .version(version) when: - v.generateSASQueryParameters(creds) + v.generateSASQueryParameters((SharedKeyCredential)creds) then: def e = thrown(IllegalArgumentException) @@ -578,7 +590,7 @@ class HelperTest extends APISpec { null | "v" | primaryCreds | "b" | "container" "c" | null | primaryCreds | "b" | "version" "c" | "v" | null | "b" | "sharedKeyCredentials" - "c" | "v" | primaryCreds | null | "blobName" + "c" | "v" | primaryCreds | null | "canonicalName" } @Unroll @@ -920,7 +932,8 @@ class HelperTest extends APISpec { ServiceSASSignatureValues sasValues = new ServiceSASSignatureValues() .permissions("r") - .containerName("container") + .canonicalName(String.format("/blob/%s/container/blob", primaryCreds.accountName())) + .resource(Constants.UrlConstants.SAS_BLOB_CONSTANT) parts.sasQueryParameters(sasValues.generateSASQueryParameters(primaryCreds)) From be429c3395a13333af2347bd77bfb41bb6658664 Mon Sep 17 00:00:00 2001 From: Alan Zimmer <48699787+alzimmermsft@users.noreply.github.com> Date: Tue, 16 Jul 2019 10:48:17 -0700 Subject: [PATCH 10/19] Remove RawClients from Blobs (#4375) Removes RawClients from Storage Blobs --- .../storage/blob/AppendBlobAsyncClient.java | 47 +- .../blob/AppendBlobAsyncRawClient.java | 192 ---- .../azure/storage/blob/AppendBlobClient.java | 17 +- .../azure/storage/blob/BlobAsyncClient.java | 830 +++++++-------- .../storage/blob/BlobAsyncRawClient.java | 766 -------------- .../com/azure/storage/blob/BlobClient.java | 680 +++++-------- .../azure/storage/blob/BlobInputStream.java | 2 +- .../azure/storage/blob/BlobOutputStream.java | 4 +- .../com/azure/storage/blob/BlobURLParts.java | 8 +- .../storage/blob/BlockBlobAsyncClient.java | 50 +- .../storage/blob/BlockBlobAsyncRawClient.java | 364 ------- .../azure/storage/blob/BlockBlobClient.java | 14 +- .../storage/blob/ContainerAsyncClient.java | 955 +++++++++--------- .../storage/blob/ContainerAsyncRawClient.java | 708 ------------- .../azure/storage/blob/ContainerClient.java | 660 +++++------- .../storage/blob/ContainerRawClient.java | 652 ------------ .../storage/blob/PageBlobAsyncClient.java | 210 +++- .../storage/blob/PageBlobAsyncRawClient.java | 620 ------------ .../azure/storage/blob/PageBlobClient.java | 12 +- .../blob/ServiceSASSignatureValues.java | 111 +- .../storage/blob/StorageAsyncClient.java | 222 ++-- .../storage/blob/StorageAsyncRawClient.java | 172 ---- .../com/azure/storage/blob/StorageClient.java | 197 ++-- .../azure/storage/blob/StorageRawClient.java | 259 ----- .../credentials/SASTokenCredential.java | 2 + .../policy/SharedKeyCredentialPolicy.java | 5 +- .../java/com/azure/storage/blob/Sample.java | 6 +- 27 files changed, 1885 insertions(+), 5880 deletions(-) delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/ContainerRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncRawClient.java delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/StorageRawClient.java diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncClient.java index 7b4585580e16f..60376a3d990ab 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncClient.java @@ -5,6 +5,7 @@ import com.azure.core.http.rest.Response; import com.azure.core.http.rest.SimpleResponse; +import com.azure.core.util.Context; import com.azure.storage.blob.implementation.AzureBlobStorageBuilder; import com.azure.storage.blob.models.AppendBlobAccessConditions; import com.azure.storage.blob.models.AppendBlobItem; @@ -13,12 +14,13 @@ import com.azure.storage.blob.models.BlobRange; import com.azure.storage.blob.models.Metadata; import com.azure.storage.blob.models.SourceModifiedAccessConditions; -import io.netty.buffer.Unpooled; +import io.netty.buffer.ByteBuf; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.net.URL; -import java.nio.ByteBuffer; + +import static com.azure.storage.blob.Utility.postProcessResponse; /** @@ -45,8 +47,6 @@ * object through {@link Mono#toFuture()}. */ public final class AppendBlobAsyncClient extends BlobAsyncClient { - final AppendBlobAsyncRawClient appendBlobAsyncRawClient; - /** * Indicates the maximum number of bytes that can be sent in a call to appendBlock. */ @@ -63,7 +63,6 @@ public final class AppendBlobAsyncClient extends BlobAsyncClient { */ AppendBlobAsyncClient(AzureBlobStorageBuilder azureBlobStorageBuilder, String snapshot) { super(azureBlobStorageBuilder, snapshot); - appendBlobAsyncRawClient = new AppendBlobAsyncRawClient(azureBlobStorageBuilder.build()); } /** @@ -90,8 +89,13 @@ public Mono> create() { * A reactive response containing the information of the created appended blob. */ public Mono> create(BlobHTTPHeaders headers, Metadata metadata, BlobAccessConditions accessConditions) { - return appendBlobAsyncRawClient - .create(headers, metadata, accessConditions) + metadata = (metadata == null) ? new Metadata() : metadata; + accessConditions = (accessConditions == null) ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.appendBlobs().createWithRestResponseAsync(null, + null, 0, null, metadata, null, null, + null, null, headers, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new AppendBlobItem(rb.deserializedHeaders()))); } @@ -111,7 +115,7 @@ public Mono> create(BlobHTTPHeaders headers, Metadata m * @return * A reactive response containing the information of the append blob operation. */ - public Mono> appendBlock(Flux data, long length) { + public Mono> appendBlock(Flux data, long length) { return this.appendBlock(data, length, null); } @@ -133,10 +137,17 @@ public Mono> appendBlock(Flux data, long le * @return * A reactive response containing the information of the append blob operation. */ - public Mono> appendBlock(Flux data, long length, - AppendBlobAccessConditions appendBlobAccessConditions) { - return appendBlobAsyncRawClient - .appendBlock(data.map(Unpooled::wrappedBuffer), length, appendBlobAccessConditions) + public Mono> appendBlock(Flux data, long length, + AppendBlobAccessConditions appendBlobAccessConditions) { + appendBlobAccessConditions = appendBlobAccessConditions == null ? new AppendBlobAccessConditions() + : appendBlobAccessConditions; + + return postProcessResponse(this.azureBlobStorage.appendBlobs().appendBlockWithRestResponseAsync( + null, null, data, length, null, null, + null, null, null, null, + appendBlobAccessConditions.leaseAccessConditions(), + appendBlobAccessConditions.appendPositionAccessConditions(), + appendBlobAccessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new AppendBlobItem(rb.deserializedHeaders()))); } @@ -183,8 +194,16 @@ public Mono> appendBlockFromUrl(URL sourceURL, BlobRang public Mono> appendBlockFromUrl(URL sourceURL, BlobRange sourceRange, byte[] sourceContentMD5, AppendBlobAccessConditions destAccessConditions, SourceModifiedAccessConditions sourceAccessConditions) { - return appendBlobAsyncRawClient - .appendBlockFromUrl(sourceURL, sourceRange, sourceContentMD5, destAccessConditions, sourceAccessConditions) + sourceRange = sourceRange == null ? new BlobRange(0) : sourceRange; + destAccessConditions = destAccessConditions == null + ? new AppendBlobAccessConditions() : destAccessConditions; + + return postProcessResponse( + this.azureBlobStorage.appendBlobs().appendBlockFromUrlWithRestResponseAsync(null, null, + sourceURL, 0, sourceRange.toString(), sourceContentMD5, null, null, + destAccessConditions.leaseAccessConditions(), + destAccessConditions.appendPositionAccessConditions(), + destAccessConditions.modifiedAccessConditions(), sourceAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new AppendBlobItem(rb.deserializedHeaders()))); } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncRawClient.java deleted file mode 100644 index 15bf8e09e4d1f..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobAsyncRawClient.java +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.util.Context; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.AppendBlobAccessConditions; -import com.azure.storage.blob.models.AppendBlobsAppendBlockFromUrlResponse; -import com.azure.storage.blob.models.AppendBlobsAppendBlockResponse; -import com.azure.storage.blob.models.AppendBlobsCreateResponse; -import com.azure.storage.blob.models.BlobAccessConditions; -import com.azure.storage.blob.models.BlobHTTPHeaders; -import com.azure.storage.blob.models.BlobRange; -import com.azure.storage.blob.models.Metadata; -import com.azure.storage.blob.models.SourceModifiedAccessConditions; -import io.netty.buffer.ByteBuf; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.net.URL; - -import static com.azure.storage.blob.Utility.postProcessResponse; - - -/** - * Represents a URL to an append blob. It may be obtained by direct construction or via the create method on a - * {@link ContainerAsyncClient} object. This class does not hold any state about a particular append blob but is instead a - * convenient way of sending off appropriate requests to the resource on the service. Please refer to the - * Azure Docs - */ -final class AppendBlobAsyncRawClient extends BlobAsyncRawClient { - - /** - * Indicates the maximum number of bytes that can be sent in a call to appendBlock. - */ - public static final int MAX_APPEND_BLOCK_BYTES = 4 * Constants.MB; - - /** - * Indicates the maximum number of blocks allowed in an append blob. - */ - public static final int MAX_BLOCKS = 50000; - - /** - * Creates a {@code AppendBlobAsyncRawClient} object pointing to the account specified by the URL and using the provided - * pipeline to make HTTP requests. - */ - AppendBlobAsyncRawClient(AzureBlobStorageImpl azureBlobStorage) { - super(azureBlobStorage, null); - } - - /** - * Creates a 0-length append blob. Call AppendBlock to append data to an append blob. For more information, see - * the Azure Docs. - * - * @return Emits the successful response. - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=append_blob "Sample code for AppendBlobAsyncRawClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono create() { - return this.create(null, null, null); - } - - /** - * Creates a 0-length append blob. Call AppendBlock to append data to an append blob. For more information, see - * the Azure Docs. - * - * @param headers {@link BlobHTTPHeaders} - * @param metadata {@link Metadata} - * @param accessConditions {@link BlobAccessConditions} - * @return Emits the successful response. - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=append_blob "Sample code for AppendBlobAsyncRawClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono create(BlobHTTPHeaders headers, Metadata metadata, - BlobAccessConditions accessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.appendBlobs().createWithRestResponseAsync(null, - null, 0, null, metadata, null, null, - null, null, headers, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Commits a new block of data to the end of the existing append blob. For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - * - * @param data The data to write to the blob. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param length The exact length of the data. It is important that this value match precisely the length of the data - * emitted by the {@code Flux}. - * @return Emits the successful response. - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=append_blob "Sample code for AppendBlobAsyncRawClient.appendBlock")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono appendBlock(Flux data, long length) { - return this.appendBlock(data, length, null); - } - - /** - * Commits a new block of data to the end of the existing append blob. For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - * - * @param data The data to write to the blob. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param length The exact length of the data. It is important that this value match precisely the length of the data - * emitted by the {@code Flux}. - * @param appendBlobAccessConditions {@link AppendBlobAccessConditions} - * @return Emits the successful response. - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=append_blob "Sample code for AppendBlobAsyncRawClient.appendBlock")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono appendBlock(Flux data, long length, - AppendBlobAccessConditions appendBlobAccessConditions) { - appendBlobAccessConditions = appendBlobAccessConditions == null ? new AppendBlobAccessConditions() - : appendBlobAccessConditions; - - return postProcessResponse(this.azureBlobStorage.appendBlobs().appendBlockWithRestResponseAsync( - null, null, data, length, null, null, - null, null, null, null, - appendBlobAccessConditions.leaseAccessConditions(), - appendBlobAccessConditions.appendPositionAccessConditions(), - appendBlobAccessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Commits a new block of data from another blob to the end of this append blob. For more information, see the - * Azure Docs. - *

- * - * @param sourceURL The url to the blob that will be the source of the copy. A source blob in the same storage account can - * be authenticated via Shared Key. However, if the source is a blob in another account, the source blob - * must either be public or must be authenticated via a shared access signature. If the source blob is - * public, no authentication is required to perform the operation. - * @param sourceRange The source {@link BlobRange} to copy. - * @return Emits the successful response. - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=append_from_url "Sample code for AppendBlobAsyncRawClient.appendBlockFromUrl")] - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono appendBlockFromUrl(URL sourceURL, BlobRange sourceRange) { - return this.appendBlockFromUrl(sourceURL, sourceRange, null, null, - null); - } - - /** - * Commits a new block of data from another blob to the end of this append blob. For more information, see the - * Azure Docs. - *

- * - * @param sourceURL The url to the blob that will be the source of the copy. A source blob in the same storage account can - * be authenticated via Shared Key. However, if the source is a blob in another account, the source blob - * must either be public or must be authenticated via a shared access signature. If the source blob is - * public, no authentication is required to perform the operation. - * @param sourceRange {@link BlobRange} - * @param sourceContentMD5 An MD5 hash of the block content from the source blob. If specified, the service will calculate the MD5 - * of the received data and fail the request if it does not match the provided MD5. - * @param destAccessConditions {@link AppendBlobAccessConditions} - * @param sourceAccessConditions {@link SourceModifiedAccessConditions} - * @return Emits the successful response. - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=append_from_url "Sample code for AppendBlobAsyncRawClient.appendBlockFromUrl")] - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono appendBlockFromUrl(URL sourceURL, BlobRange sourceRange, - byte[] sourceContentMD5, AppendBlobAccessConditions destAccessConditions, - SourceModifiedAccessConditions sourceAccessConditions) { - - sourceRange = sourceRange == null ? new BlobRange(0) : sourceRange; - destAccessConditions = destAccessConditions == null - ? new AppendBlobAccessConditions() : destAccessConditions; - - return postProcessResponse( - this.azureBlobStorage.appendBlobs().appendBlockFromUrlWithRestResponseAsync(null, null, - sourceURL, 0, sourceRange.toString(), sourceContentMD5, null, null, - destAccessConditions.leaseAccessConditions(), - destAccessConditions.appendPositionAccessConditions(), - destAccessConditions.modifiedAccessConditions(), sourceAccessConditions, Context.NONE)); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobClient.java index 47a379649659f..6be2bf7f4e5aa 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobClient.java @@ -11,13 +11,14 @@ import com.azure.storage.blob.models.BlobRange; import com.azure.storage.blob.models.Metadata; import com.azure.storage.blob.models.SourceModifiedAccessConditions; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import java.io.InputStream; import java.net.URL; -import java.nio.ByteBuffer; import java.time.Duration; @@ -37,18 +38,17 @@ * for more information. */ public final class AppendBlobClient extends BlobClient { - - AppendBlobAsyncClient appendBlobAsyncClient; + private AppendBlobAsyncClient appendBlobAsyncClient; /** * Indicates the maximum number of bytes that can be sent in a call to appendBlock. */ - public static final int MAX_APPEND_BLOCK_BYTES = 4 * Constants.MB; + public static final int MAX_APPEND_BLOCK_BYTES = AppendBlobAsyncClient.MAX_APPEND_BLOCK_BYTES; /** * Indicates the maximum number of blocks allowed in an append blob. */ - public static final int MAX_BLOCKS = 50000; + public static final int MAX_BLOCKS = AppendBlobAsyncClient.MAX_BLOCKS; /** * Package-private constructor for use by {@link AppendBlobClientBuilder}. @@ -58,7 +58,7 @@ public final class AppendBlobClient extends BlobClient { super(appendBlobAsyncClient); this.appendBlobAsyncClient = appendBlobAsyncClient; } - + /** * Creates and opens an output stream to write data to the append blob. If the blob already exists on the service, * it will be overwritten. @@ -160,7 +160,7 @@ public Response appendBlock(InputStream data, long length) { */ public Response appendBlock(InputStream data, long length, AppendBlobAccessConditions appendBlobAccessConditions, Duration timeout) { - Flux fbb = Flux.range(0, (int) Math.ceil((double) length / (double) MAX_APPEND_BLOCK_BYTES)) + Flux fbb = Flux.range(0, (int) Math.ceil((double) length / (double) MAX_APPEND_BLOCK_BYTES)) .map(i -> i * MAX_APPEND_BLOCK_BYTES) .concatMap(pos -> Mono.fromCallable(() -> { long count = pos + MAX_APPEND_BLOCK_BYTES > length ? length - pos : MAX_APPEND_BLOCK_BYTES; @@ -169,7 +169,8 @@ public Response appendBlock(InputStream data, long length, while (read < count) { read += data.read(cache, read, (int) count - read); } - return ByteBuffer.wrap(cache); + + return ByteBufAllocator.DEFAULT.buffer((int) count).writeBytes(cache); })); Mono> response = appendBlobAsyncClient.appendBlock(fbb.subscribeOn(Schedulers.elastic()), length, appendBlobAccessConditions); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java index 5042f9e782af5..2601107aaa0b6 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncClient.java @@ -9,7 +9,9 @@ import com.azure.core.http.rest.VoidResponse; import com.azure.core.implementation.http.UrlBuilder; import com.azure.core.implementation.util.FluxUtil; +import com.azure.core.util.Context; import com.azure.storage.blob.implementation.AzureBlobStorageBuilder; +import com.azure.storage.blob.implementation.AzureBlobStorageImpl; import com.azure.storage.blob.models.AccessTier; import com.azure.storage.blob.models.BlobAccessConditions; import com.azure.storage.blob.models.BlobHTTPHeaders; @@ -20,6 +22,7 @@ import com.azure.storage.blob.models.Metadata; import com.azure.storage.blob.models.ModifiedAccessConditions; import com.azure.storage.blob.models.ReliableDownloadOptions; +import com.azure.storage.blob.models.SourceModifiedAccessConditions; import com.azure.storage.blob.models.StorageAccountInfo; import com.azure.storage.blob.models.UserDelegationKey; import com.azure.storage.common.credentials.SharedKeyCredential; @@ -41,82 +44,89 @@ import java.util.ArrayList; import java.util.List; +import static com.azure.storage.blob.Utility.postProcessResponse; + /** - * Client to a blob of any type: block, append, or page. It may only be instantiated through a {@link BlobClientBuilder} or via - * the method {@link ContainerAsyncClient#getBlobAsyncClient(String)}. This class does not hold any state about a particular - * blob, but is instead a convenient way of sending appropriate requests to the resource on the service. + * Client to a blob of any type: block, append, or page. It may only be instantiated through a {@link BlobClientBuilder} + * or via the method {@link ContainerAsyncClient#getBlobAsyncClient(String)}. This class does not hold any state about a + * particular blob, but is instead a convenient way of sending appropriate requests to the resource on the service. * *

* This client offers the ability to download blobs. Note that uploading data is specific to each type of blob. Please * refer to the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link AppendBlobClient} for upload options. This - * client can be converted into one of these clients easily through the methods {@link #asBlockBlobAsyncClient}, - * {@link #asPageBlobAsyncClient}, and {@link #asAppendBlobAsyncClient()}. + * client can be converted into one of these clients easily through the methods {@link #asBlockBlobAsyncClient}, {@link + * #asPageBlobAsyncClient}, and {@link #asAppendBlobAsyncClient()}. * *

* This client contains operations on a blob. Operations on a container are available on {@link ContainerAsyncClient}, * and operations on the service are available on {@link StorageAsyncClient}. * *

- * Please refer to the Azure Docs - * for more information. + * Please refer to the Azure + * Docs for more information. * *

- * Note this client is an async client that returns reactive responses from Spring Reactor Core - * project (https://projectreactor.io/). Calling the methods in this client will NOT - * start the actual network operation, until {@code .subscribe()} is called on the reactive response. - * You can simply convert one of these responses to a {@link java.util.concurrent.CompletableFuture} - * object through {@link Mono#toFuture()}. + * Note this client is an async client that returns reactive responses from Spring Reactor Core project + * (https://projectreactor.io/). Calling the methods in this client will NOT start the actual network + * operation, until {@code .subscribe()} is called on the reactive response. You can simply convert one of these + * responses to a {@link java.util.concurrent.CompletableFuture} object through {@link Mono#toFuture()}. */ public class BlobAsyncClient { private static final int BLOB_DEFAULT_DOWNLOAD_BLOCK_SIZE = 4 * Constants.MB; private static final int BLOB_MAX_DOWNLOAD_BLOCK_SIZE = 100 * Constants.MB; - final BlobAsyncRawClient blobAsyncRawClient; + protected final AzureBlobStorageImpl azureBlobStorage; + protected final String snapshot; /** * Package-private constructor for use by {@link BlobClientBuilder}. + * * @param azureBlobStorageBuilder the API client builder for blob storage API */ BlobAsyncClient(AzureBlobStorageBuilder azureBlobStorageBuilder, String snapshot) { - this.blobAsyncRawClient = new BlobAsyncRawClient(azureBlobStorageBuilder.build(), snapshot); + this.azureBlobStorage = azureBlobStorageBuilder.build(); + this.snapshot = snapshot; } /** * Creates a new {@link BlockBlobAsyncClient} to this resource, maintaining configurations. Only do this for blobs * that are known to be block blobs. * - * @return - * A {@link BlockBlobAsyncClient} to this resource. + * @return A {@link BlockBlobAsyncClient} to this resource. */ public BlockBlobAsyncClient asBlockBlobAsyncClient() { - return new BlockBlobAsyncClient(new AzureBlobStorageBuilder().url(getBlobUrl().toString()).pipeline(blobAsyncRawClient.azureBlobStorage.httpPipeline()), blobAsyncRawClient.snapshot); + return new BlockBlobAsyncClient(new AzureBlobStorageBuilder() + .url(getBlobUrl().toString()) + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** * Creates a new {@link AppendBlobAsyncClient} to this resource, maintaining configurations. Only do this for blobs * that are known to be append blobs. * - * @return - * A {@link AppendBlobAsyncClient} to this resource. + * @return A {@link AppendBlobAsyncClient} to this resource. */ public AppendBlobAsyncClient asAppendBlobAsyncClient() { - return new AppendBlobAsyncClient(new AzureBlobStorageBuilder().url(getBlobUrl().toString()).pipeline(blobAsyncRawClient.azureBlobStorage.httpPipeline()), blobAsyncRawClient.snapshot); + return new AppendBlobAsyncClient(new AzureBlobStorageBuilder() + .url(getBlobUrl().toString()) + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** * Creates a new {@link PageBlobAsyncClient} to this resource, maintaining configurations. Only do this for blobs * that are known to be page blobs. * - * @return - * A {@link PageBlobAsyncClient} to this resource. + * @return A {@link PageBlobAsyncClient} to this resource. */ public PageBlobAsyncClient asPageBlobAsyncClient() { - return new PageBlobAsyncClient(new AzureBlobStorageBuilder().url(getBlobUrl().toString()).pipeline(blobAsyncRawClient.azureBlobStorage.httpPipeline()), blobAsyncRawClient.snapshot); + return new PageBlobAsyncClient(new AzureBlobStorageBuilder() + .url(getBlobUrl().toString()) + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** - * Initializes a {@link ContainerAsyncClient} object pointing to the container this blob is in. This method does - * not create a container. It simply constructs the URL to the container and offers access to methods relevant to + * Initializes a {@link ContainerAsyncClient} object pointing to the container this blob is in. This method does not + * create a container. It simply constructs the URL to the container and offers access to methods relevant to * containers. * * @return A {@link ContainerAsyncClient} object pointing to the container containing the blob @@ -125,31 +135,31 @@ public ContainerAsyncClient getContainerAsyncClient() { BlobURLParts parts = URLParser.parse(getBlobUrl()); return new ContainerAsyncClient(new AzureBlobStorageBuilder() .url(String.format("%s://%s/%s", parts.scheme(), parts.host(), parts.containerName())) - .pipeline(blobAsyncRawClient.azureBlobStorage.httpPipeline())); + .pipeline(azureBlobStorage.httpPipeline())); } /** * Gets the URL of the blob represented by this client. + * * @return the URL. * @throws RuntimeException If the blob is using a malformed URL. */ public URL getBlobUrl() { try { - UrlBuilder urlBuilder = UrlBuilder.parse(blobAsyncRawClient.azureBlobStorage.url()); - if (blobAsyncRawClient.snapshot != null) { - urlBuilder.query("snapshot=" + blobAsyncRawClient.snapshot); + UrlBuilder urlBuilder = UrlBuilder.parse(azureBlobStorage.url()); + if (snapshot != null) { + urlBuilder.query("snapshot=" + snapshot); } return urlBuilder.toURL(); } catch (MalformedURLException e) { - throw new RuntimeException(String.format("Invalid URL on %s: %s" + getClass().getSimpleName(), blobAsyncRawClient.azureBlobStorage.url()), e); + throw new RuntimeException(String.format("Invalid URL on %s: %s" + getClass().getSimpleName(), azureBlobStorage.url()), e); } } /** * Gets if the blob this client represents exists in the cloud. * - * @return - * true if the blob exists, false if it doesn't + * @return true if the blob exists, false if it doesn't */ public Mono> exists() { return this.getProperties() @@ -164,11 +174,8 @@ public Mono> exists() { * Copies the data at the source URL to a blob. For more information, see the * Azure Docs * - * @param sourceURL - * The source URL to copy from. URLs outside of Azure may only be copied to block blobs. - * - * @return - * A reactive response containing the copy ID for the long running operation. + * @param sourceURL The source URL to copy from. URLs outside of Azure may only be copied to block blobs. + * @return A reactive response containing the copy ID for the long running operation. */ public Mono> startCopyFromURL(URL sourceURL) { return this.startCopyFromURL(sourceURL, null, null, null); @@ -178,37 +185,41 @@ public Mono> startCopyFromURL(URL sourceURL) { * Copies the data at the source URL to a blob. For more information, see the * Azure Docs * - * @param sourceURL - * The source URL to copy from. URLs outside of Azure may only be copied to block blobs. - * @param metadata - * {@link Metadata} - * @param sourceModifiedAccessConditions - * {@link ModifiedAccessConditions} against the source. Standard HTTP Access conditions related to the - * modification of data. ETag and LastModifiedTime are used to construct conditions related to when the blob - * was changed relative to the given request. The request will fail if the specified condition is not - * satisfied. - * @param destAccessConditions - * {@link BlobAccessConditions} against the destination. - * - * @return - * A reactive response containing the copy ID for the long running operation. + * @param sourceURL The source URL to copy from. URLs outside of Azure may only be copied to block blobs. + * @param metadata {@link Metadata} + * @param sourceModifiedAccessConditions {@link ModifiedAccessConditions} against the source. Standard HTTP Access + * conditions related to the modification of data. ETag and LastModifiedTime are used to construct conditions + * related to when the blob was changed relative to the given request. The request will fail if the specified + * condition is not satisfied. + * @param destAccessConditions {@link BlobAccessConditions} against the destination. + * @return A reactive response containing the copy ID for the long running operation. */ public Mono> startCopyFromURL(URL sourceURL, Metadata metadata, - ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions) { - return blobAsyncRawClient - .startCopyFromURL(sourceURL, metadata, sourceModifiedAccessConditions, destAccessConditions) + ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions) { + metadata = metadata == null ? new Metadata() : metadata; + sourceModifiedAccessConditions = sourceModifiedAccessConditions == null + ? new ModifiedAccessConditions() : sourceModifiedAccessConditions; + destAccessConditions = destAccessConditions == null ? new BlobAccessConditions() : destAccessConditions; + + // We want to hide the SourceAccessConditions type from the user for consistency's sake, so we convert here. + SourceModifiedAccessConditions sourceConditions = new SourceModifiedAccessConditions() + .sourceIfModifiedSince(sourceModifiedAccessConditions.ifModifiedSince()) + .sourceIfUnmodifiedSince(sourceModifiedAccessConditions.ifUnmodifiedSince()) + .sourceIfMatch(sourceModifiedAccessConditions.ifMatch()) + .sourceIfNoneMatch(sourceModifiedAccessConditions.ifNoneMatch()); + + return postProcessResponse(this.azureBlobStorage.blobs().startCopyFromURLWithRestResponseAsync( + null, null, sourceURL, null, metadata, null, sourceConditions, + destAccessConditions.modifiedAccessConditions(), destAccessConditions.leaseAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().copyId())); } /** * Stops a pending copy that was previously started and leaves a destination blob with 0 length and metadata. * - * @param copyId - * The id of the copy operation to abort. Returned as the {@code copyId} field on the {@link - * BlobStartCopyFromURLHeaders} object. - * - * @return - * A reactive response signalling completion. + * @param copyId The id of the copy operation to abort. Returned as the {@code copyId} field on the {@link + * BlobStartCopyFromURLHeaders} object. + * @return A reactive response signalling completion. */ public Mono abortCopyFromURL(String copyId) { return this.abortCopyFromURL(copyId, null); @@ -217,30 +228,23 @@ public Mono abortCopyFromURL(String copyId) { /** * Stops a pending copy that was previously started and leaves a destination blob with 0 length and metadata. * - * @param copyId - * The id of the copy operation to abort. Returned as the {@code copyId} field on the {@link - * BlobStartCopyFromURLHeaders} object. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return - * A reactive response signalling completion. + * @param copyId The id of the copy operation to abort. Returned as the {@code copyId} field on the {@link + * BlobStartCopyFromURLHeaders} object. + * @param leaseAccessConditions By setting lease access conditions, requests will fail if the provided lease does + * not match the active lease on the blob. + * @return A reactive response signalling completion. */ public Mono abortCopyFromURL(String copyId, LeaseAccessConditions leaseAccessConditions) { - return blobAsyncRawClient - .abortCopyFromURL(copyId, leaseAccessConditions) + return postProcessResponse(this.azureBlobStorage.blobs().abortCopyFromURLWithRestResponseAsync( + null, null, copyId, null, null, leaseAccessConditions, Context.NONE)) .map(VoidResponse::new); } /** * Copies the data at the source URL to a blob and waits for the copy to complete before returning a response. * - * @param copySource - * The source URL to copy from. - * - * @return - * A reactive response containing the copy ID for the long running operation. + * @param copySource The source URL to copy from. + * @return A reactive response containing the copy ID for the long running operation. */ public Mono> copyFromURL(URL copySource) { return this.copyFromURL(copySource, null, null, null); @@ -249,63 +253,110 @@ public Mono> copyFromURL(URL copySource) { /** * Copies the data at the source URL to a blob and waits for the copy to complete before returning a response. * - * @param copySource - * The source URL to copy from. URLs outside of Azure may only be copied to block blobs. - * @param metadata - * {@link Metadata} - * @param sourceModifiedAccessConditions - * {@link ModifiedAccessConditions} against the source. Standard HTTP Access conditions related to the - * modification of data. ETag and LastModifiedTime are used to construct conditions related to when the blob - * was changed relative to the given request. The request will fail if the specified condition is not - * satisfied. - * @param destAccessConditions - * {@link BlobAccessConditions} against the destination. - * - * @return - * A reactive response containing the copy ID for the long running operation. + * @param copySource The source URL to copy from. URLs outside of Azure may only be copied to block blobs. + * @param metadata {@link Metadata} + * @param sourceModifiedAccessConditions {@link ModifiedAccessConditions} against the source. Standard HTTP Access + * conditions related to the modification of data. ETag and LastModifiedTime are used to construct conditions + * related to when the blob was changed relative to the given request. The request will fail if the specified + * condition is not satisfied. + * @param destAccessConditions {@link BlobAccessConditions} against the destination. + * @return A reactive response containing the copy ID for the long running operation. */ public Mono> copyFromURL(URL copySource, Metadata metadata, - ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions) { - return blobAsyncRawClient - .syncCopyFromURL(copySource, metadata, sourceModifiedAccessConditions, destAccessConditions) + ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions) { + metadata = metadata == null ? new Metadata() : metadata; + sourceModifiedAccessConditions = sourceModifiedAccessConditions == null + ? new ModifiedAccessConditions() : sourceModifiedAccessConditions; + destAccessConditions = destAccessConditions == null ? new BlobAccessConditions() : destAccessConditions; + + // We want to hide the SourceAccessConditions type from the user for consistency's sake, so we convert here. + SourceModifiedAccessConditions sourceConditions = new SourceModifiedAccessConditions() + .sourceIfModifiedSince(sourceModifiedAccessConditions.ifModifiedSince()) + .sourceIfUnmodifiedSince(sourceModifiedAccessConditions.ifUnmodifiedSince()) + .sourceIfMatch(sourceModifiedAccessConditions.ifMatch()) + .sourceIfNoneMatch(sourceModifiedAccessConditions.ifNoneMatch()); + + return postProcessResponse(this.azureBlobStorage.blobs().copyFromURLWithRestResponseAsync( + null, null, copySource, null, metadata, null, sourceConditions, + destAccessConditions.modifiedAccessConditions(), destAccessConditions.leaseAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().copyId())); } /** - * Reads the entire blob. Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link AppendBlobClient}. + * Reads the entire blob. Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or + * {@link AppendBlobClient}. * - * @return - * A reactive response containing the blob data. + * @return A reactive response containing the blob data. */ public Mono>> download() { return this.download(null, null, false, null); } /** - * Reads a range of bytes from a blob. Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link AppendBlobClient}. + * Reads a range of bytes from a blob. Uploading data must be done from the {@link BlockBlobClient}, {@link + * PageBlobClient}, or {@link AppendBlobClient}. * - * @param range - * {@link BlobRange} - * @param accessConditions - * {@link BlobAccessConditions} - * @param rangeGetContentMD5 - * Whether the contentMD5 for the specified blob range should be returned. + * @param range {@link BlobRange} + * @param accessConditions {@link BlobAccessConditions} + * @param rangeGetContentMD5 Whether the contentMD5 for the specified blob range should be returned. * @param options {@link ReliableDownloadOptions} - * * @return A reactive response containing the blob data. */ public Mono>> download(BlobRange range, BlobAccessConditions accessConditions, - boolean rangeGetContentMD5, ReliableDownloadOptions options) { - return blobAsyncRawClient - .download(range, accessConditions, rangeGetContentMD5) + boolean rangeGetContentMD5, ReliableDownloadOptions options) { + return this.download(range, accessConditions, rangeGetContentMD5) .map(response -> new SimpleResponse<>( response.rawResponse(), response.body(options).map(ByteBuf::nioBuffer).switchIfEmpty(Flux.just(ByteBuffer.allocate(0))))); } + /** + * Reads a range of bytes from a blob. The response also includes the blob's properties and metadata. For more + * information, see the Azure Docs. + *

+ * Note that the response body has reliable download functionality built in, meaning that a failed download stream + * will be automatically retried. This behavior may be configured with {@link ReliableDownloadOptions}. + * + * @param range {@link BlobRange} + * @param accessConditions {@link BlobAccessConditions} + * @param rangeGetContentMD5 Whether the contentMD5 for the specified blob range should be returned. + * @return Emits the successful response. + * @apiNote ## Sample Code \n [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=upload_download + * "Sample code for BlobAsyncClient.download")] \n For more samples, please see the [Samples + * file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) + */ + Mono download(BlobRange range, BlobAccessConditions accessConditions, boolean rangeGetContentMD5) { + range = range == null ? new BlobRange(0) : range; + Boolean getMD5 = rangeGetContentMD5 ? rangeGetContentMD5 : null; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + HTTPGetterInfo info = new HTTPGetterInfo() + .offset(range.offset()) + .count(range.count()) + .eTag(accessConditions.modifiedAccessConditions().ifMatch()); + + // TODO: range is BlobRange but expected as String + // TODO: figure out correct response + return postProcessResponse(this.azureBlobStorage.blobs().downloadWithRestResponseAsync( + null, null, snapshot, null, null, range.toHeaderValue(), getMD5, + null, null, null, null, + accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) + // Convert the autorest response to a DownloadAsyncResponse, which enable reliable download. + .map(response -> { + // If there wasn't an etag originally specified, lock on the one returned. + info.eTag(response.deserializedHeaders().eTag()); + return new DownloadAsyncResponse(response, info, + // In the event of a stream failure, make a new request to pick up where we left off. + newInfo -> + this.download(new BlobRange(newInfo.offset(), newInfo.count()), + new BlobAccessConditions().modifiedAccessConditions( + new ModifiedAccessConditions().ifMatch(info.eTag())), false)); + }); + } + /** * Downloads the entire blob into a file specified by the path. The file will be created if it doesn't exist. - * Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link AppendBlobClient}. + * Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link + * AppendBlobClient}. *

* This method makes an extra HTTP call to get the length of the blob in the beginning. To avoid this extra call, * use the other overload providing the {@link BlobRange} parameter. @@ -319,21 +370,17 @@ public Mono downloadToFile(String filePath) { /** * Downloads a range of bytes blob into a file specified by the path. The file will be created if it doesn't exist. - * Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link AppendBlobClient}. + * Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link + * AppendBlobClient}. *

* This method makes an extra HTTP call to get the length of the blob in the beginning. To avoid this extra call, * provide the {@link BlobRange} parameter. * - * @param filePath - * A non-null {@link OutputStream} instance where the downloaded data will be written. - * @param range - * {@link BlobRange} - * @param blockSize - * the size of a chunk to download at a time, in bytes - * @param accessConditions - * {@link BlobAccessConditions} - * @param rangeGetContentMD5 - * Whether the contentMD5 for the specified blob range should be returned. + * @param filePath A non-null {@link OutputStream} instance where the downloaded data will be written. + * @param range {@link BlobRange} + * @param blockSize the size of a chunk to download at a time, in bytes + * @param accessConditions {@link BlobAccessConditions} + * @param rangeGetContentMD5 Whether the contentMD5 for the specified blob range should be returned. * @param options {@link ReliableDownloadOptions} * @return An empty response * @throws IllegalArgumentException If {@code blockSize} is less than 0 or greater than 100MB. @@ -349,8 +396,7 @@ public Mono downloadToFile(String filePath, BlobRange range, Integer block channel -> Mono.justOrEmpty(range) .switchIfEmpty(getFullBlobRange(accessConditions)) .flatMapMany(rg -> Flux.fromIterable(sliceBlobRange(rg, blockSize))) - .flatMap(chunk -> blobAsyncRawClient - .download(chunk, accessConditions, rangeGetContentMD5) + .flatMap(chunk -> this.download(chunk, accessConditions, rangeGetContentMD5) .subscribeOn(Schedulers.elastic()) .flatMap(dar -> FluxUtil.bytebufStreamToFile(dar.body(options), channel, chunk.offset() - (range == null ? 0 : range.offset())))) .then(), this::downloadToFileCleanup); @@ -396,8 +442,7 @@ private List sliceBlobRange(BlobRange blobRange, Integer blockSize) { /** * Deletes the specified blob or snapshot. Note that deleting a blob also deletes all its snapshots. * - * @return - * A reactive response signalling completion. + * @return A reactive response signalling completion. */ public Mono delete() { return this.delete(null, null); @@ -406,28 +451,27 @@ public Mono delete() { /** * Deletes the specified blob or snapshot. Note that deleting a blob also deletes all its snapshots. * - * @param deleteBlobSnapshotOptions - * Specifies the behavior for deleting the snapshots on this blob. {@code Include} will delete the base blob - * and all snapshots. {@code Only} will delete only the snapshots. If a snapshot is being deleted, you must - * pass null. - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return - * A reactive response signalling completion. + * @param deleteBlobSnapshotOptions Specifies the behavior for deleting the snapshots on this blob. {@code Include} + * will delete the base blob and all snapshots. {@code Only} will delete only the snapshots. If a snapshot is being + * deleted, you must pass null. + * @param accessConditions {@link BlobAccessConditions} + * @return A reactive response signalling completion. */ public Mono delete(DeleteSnapshotsOptionType deleteBlobSnapshotOptions, - BlobAccessConditions accessConditions) { - return blobAsyncRawClient - .delete(deleteBlobSnapshotOptions, accessConditions) + BlobAccessConditions accessConditions) { + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blobs().deleteWithRestResponseAsync( + null, null, snapshot, null, null, deleteBlobSnapshotOptions, + null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), + Context.NONE)) .map(VoidResponse::new); } /** * Returns the blob's metadata and properties. * - * @return - * A reactive response containing the blob properties and metadata. + * @return A reactive response containing the blob properties and metadata. */ public Mono> getProperties() { return this.getProperties(null); @@ -436,93 +480,87 @@ public Mono> getProperties() { /** * Returns the blob's metadata and properties. * - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return - * A reactive response containing the blob properties and metadata. + * @param accessConditions {@link BlobAccessConditions} + * @return A reactive response containing the blob properties and metadata. */ public Mono> getProperties(BlobAccessConditions accessConditions) { - return blobAsyncRawClient - .getProperties(accessConditions) + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blobs().getPropertiesWithRestResponseAsync( + null, null, snapshot, null, null, null, + null, null, null, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new BlobProperties(rb.deserializedHeaders()))); } /** - * Changes a blob's HTTP header properties. if only one HTTP header is updated, the - * others will all be erased. In order to preserve existing values, they must be - * passed alongside the header being changed. For more information, see the + * Changes a blob's HTTP header properties. if only one HTTP header is updated, the others will all be erased. In + * order to preserve existing values, they must be passed alongside the header being changed. For more information, + * see the * Azure Docs. * - * @param headers - * {@link BlobHTTPHeaders} - * - * @return - * A reactive response signalling completion. + * @param headers {@link BlobHTTPHeaders} + * @return A reactive response signalling completion. */ public Mono setHTTPHeaders(BlobHTTPHeaders headers) { return this.setHTTPHeaders(headers, null); } /** - * Changes a blob's HTTP header properties. if only one HTTP header is updated, the - * others will all be erased. In order to preserve existing values, they must be - * passed alongside the header being changed. For more information, see the + * Changes a blob's HTTP header properties. if only one HTTP header is updated, the others will all be erased. In + * order to preserve existing values, they must be passed alongside the header being changed. For more information, + * see the * Azure Docs. * - * @param headers - * {@link BlobHTTPHeaders} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return - * A reactive response signalling completion. + * @param headers {@link BlobHTTPHeaders} + * @param accessConditions {@link BlobAccessConditions} + * @return A reactive response signalling completion. */ public Mono setHTTPHeaders(BlobHTTPHeaders headers, BlobAccessConditions accessConditions) { - return blobAsyncRawClient - .setHTTPHeaders(headers, accessConditions) + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blobs().setHTTPHeadersWithRestResponseAsync( + null, null, null, null, headers, + accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) .map(VoidResponse::new); } /** - * Changes a blob's metadata. The specified metadata in this method will replace existing - * metadata. If old values must be preserved, they must be downloaded and included in the - * call to this method. For more information, see the Azure Docs. - * - * @param metadata - * {@link Metadata} + * Changes a blob's metadata. The specified metadata in this method will replace existing metadata. If old values + * must be preserved, they must be downloaded and included in the call to this method. For more information, see the + * Azure Docs. * - * @return - * A reactive response signalling completion. + * @param metadata {@link Metadata} + * @return A reactive response signalling completion. */ public Mono setMetadata(Metadata metadata) { return this.setMetadata(metadata, null); } /** - * Changes a blob's metadata. The specified metadata in this method will replace existing - * metadata. If old values must be preserved, they must be downloaded and included in the - * call to this method. For more information, see the Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} + * Changes a blob's metadata. The specified metadata in this method will replace existing metadata. If old values + * must be preserved, they must be downloaded and included in the call to this method. For more information, see the + * Azure Docs. * - * @return - * A reactive response signalling completion. + * @param metadata {@link Metadata} + * @param accessConditions {@link BlobAccessConditions} + * @return A reactive response signalling completion. */ public Mono setMetadata(Metadata metadata, BlobAccessConditions accessConditions) { - return blobAsyncRawClient - .setMetadata(metadata, accessConditions) + metadata = metadata == null ? new Metadata() : metadata; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blobs().setMetadataWithRestResponseAsync( + null, null, null, metadata, null, null, + null, null, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .map(VoidResponse::new); } /** * Creates a read-only snapshot of a blob. * - * @return - * A reactive response containing the ID of the new snapshot. + * @return A reactive response containing the ID of the new snapshot. */ public Mono> createSnapshot() { return this.createSnapshot(null, null); @@ -531,30 +569,29 @@ public Mono> createSnapshot() { /** * Creates a read-only snapshot of a blob. * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return - * A reactive response containing the ID of the new snapshot. + * @param metadata {@link Metadata} + * @param accessConditions {@link BlobAccessConditions} + * @return A reactive response containing the ID of the new snapshot. */ public Mono> createSnapshot(Metadata metadata, BlobAccessConditions accessConditions) { - return blobAsyncRawClient - .createSnapshot(metadata, accessConditions) + metadata = metadata == null ? new Metadata() : metadata; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blobs().createSnapshotWithRestResponseAsync( + null, null, null, metadata, null, null, + null, null, accessConditions.modifiedAccessConditions(), + accessConditions.leaseAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().snapshot())); } /** * Sets the tier on a blob. The operation is allowed on a page blob in a premium storage account or a block blob in * a blob storage or GPV2 account. A premium page blob's tier determines the allowed size, IOPS, and bandwidth of - * the blob. A block blob's tier determines the Hot/Cool/Archive storage type. This does not update the blob's etag. - * - * @param tier - * The new tier for the blob. + * the blob. A block blob's tier determines the Hot/Cool/Archive storage type. This does not update the blob's + * etag. * - * @return - * A reactive response signalling completion. + * @param tier The new tier for the blob. + * @return A reactive response signalling completion. */ public Mono setTier(AccessTier tier) { return this.setTier(tier, null); @@ -563,32 +600,30 @@ public Mono setTier(AccessTier tier) { /** * Sets the tier on a blob. The operation is allowed on a page blob in a premium storage account or a block blob in * a blob storage or GPV2 account. A premium page blob's tier determines the allowed size, IOPS, and bandwidth of - * the blob. A block blob's tier determines the Hot/Cool/Archive storage type. This does not update the blob's etag. + * the blob. A block blob's tier determines the Hot/Cool/Archive storage type. This does not update the blob's + * etag. * - * @param tier - * The new tier for the blob. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return - * A reactive response signalling completion. + * @param tier The new tier for the blob. + * @param leaseAccessConditions By setting lease access conditions, requests will fail if the provided lease does + * not match the active lease on the blob. + * @return A reactive response signalling completion. */ public Mono setTier(AccessTier tier, LeaseAccessConditions leaseAccessConditions) { - return blobAsyncRawClient - .setTier(tier, leaseAccessConditions) + Utility.assertNotNull("tier", tier); + + return postProcessResponse(this.azureBlobStorage.blobs().setTierWithRestResponseAsync( + null, null, tier, null, null, leaseAccessConditions, Context.NONE)) .map(VoidResponse::new); } /** * Undelete restores the content and metadata of a soft-deleted blob and/or any associated soft-deleted snapshots. * - * @return - * A reactive response signalling completion. + * @return A reactive response signalling completion. */ public Mono undelete() { - return blobAsyncRawClient - .undelete() + return postProcessResponse(this.azureBlobStorage.blobs().undeleteWithRestResponseAsync(null, + null, Context.NONE)) .map(VoidResponse::new); } @@ -596,14 +631,10 @@ public Mono undelete() { * Acquires a lease on the blob for write and delete operations. The lease duration must be between 15 to 60 * seconds, or infinite (-1). * - * @param proposedId - * A {@code String} in any valid GUID format. May be null. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that - * never expires. A non-infinite lease can be between 15 and 60 seconds. - * - * @return - * A reactive response containing the lease ID. + * @param proposedId A {@code String} in any valid GUID format. May be null. + * @param duration The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. A + * non-infinite lease can be between 15 and 60 seconds. + * @return A reactive response containing the lease ID. */ public Mono> acquireLease(String proposedId, int duration) { return this.acquireLease(proposedId, duration, null); @@ -613,33 +644,33 @@ public Mono> acquireLease(String proposedId, int duration) { * Acquires a lease on the blob for write and delete operations. The lease duration must be between 15 to 60 * seconds, or infinite (-1). * - * @param proposedID - * A {@code String} in any valid GUID format. May be null. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that - * never expires. A non-infinite lease can be between 15 and 60 seconds. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return - * A reactive response containing the lease ID. + * @param proposedID A {@code String} in any valid GUID format. May be null. + * @param duration The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. A + * non-infinite lease can be between 15 and 60 seconds. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @return A reactive response containing the lease ID. + * @throws IllegalArgumentException If {@code duration} is outside the bounds of 15 to 60 or isn't -1. */ public Mono> acquireLease(String proposedID, int duration, ModifiedAccessConditions modifiedAccessConditions) { - return blobAsyncRawClient - .acquireLease(proposedID, duration, modifiedAccessConditions) + if (!(duration == -1 || (duration >= 15 && duration <= 60))) { + // Throwing is preferred to Mono.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("Duration must be -1 or between 15 and 60."); + } + + return postProcessResponse(this.azureBlobStorage.blobs().acquireLeaseWithRestResponseAsync( + null, null, null, duration, proposedID, null, + modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseId())); } /** * Renews the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * - * @return - * A reactive response containing the renewed lease ID. + * @param leaseID The leaseId of the active lease on the blob. + * @return A reactive response containing the renewed lease ID. */ public Mono> renewLease(String leaseID) { return this.renewLease(leaseID, null); @@ -648,30 +679,23 @@ public Mono> renewLease(String leaseID) { /** * Renews the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return - * A reactive response containing the renewed lease ID. + * @param leaseID The leaseId of the active lease on the blob. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @return A reactive response containing the renewed lease ID. */ public Mono> renewLease(String leaseID, ModifiedAccessConditions modifiedAccessConditions) { - return blobAsyncRawClient - .renewLease(leaseID, modifiedAccessConditions) + return postProcessResponse(this.azureBlobStorage.blobs().renewLeaseWithRestResponseAsync(null, + null, leaseID, null, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseId())); } /** * Releases the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * - * @return - * A reactive response signalling completion. + * @param leaseID The leaseId of the active lease on the blob. + * @return A reactive response signalling completion. */ public Mono releaseLease(String leaseID) { return this.releaseLease(leaseID, null); @@ -680,19 +704,15 @@ public Mono releaseLease(String leaseID) { /** * Releases the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return - * A reactive response signalling completion. + * @param leaseID The leaseId of the active lease on the blob. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @return A reactive response signalling completion. */ public Mono releaseLease(String leaseID, ModifiedAccessConditions modifiedAccessConditions) { - return blobAsyncRawClient - .releaseLease(leaseID, modifiedAccessConditions) + return postProcessResponse(this.azureBlobStorage.blobs().releaseLeaseWithRestResponseAsync(null, + null, leaseID, null, null, modifiedAccessConditions, Context.NONE)) .map(VoidResponse::new); } @@ -700,8 +720,7 @@ public Mono releaseLease(String leaseID, ModifiedAccessConditions * BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1) constant * to break a fixed-duration lease when it expires or an infinite lease immediately. * - * @return - * A reactive response containing the remaining time in the broken lease in seconds. + * @return A reactive response containing the remaining time in the broken lease in seconds. */ public Mono> breakLease() { return this.breakLease(null, null); @@ -711,90 +730,74 @@ public Mono> breakLease() { * BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1) constant * to break a fixed-duration lease when it expires or an infinite lease immediately. * - * @param breakPeriodInSeconds - * An optional {@code Integer} representing the proposed duration of seconds that the lease should continue - * before it is broken, between 0 and 60 seconds. This break period is only used if it is shorter than the - * time remaining on the lease. If longer, the time remaining on the lease is used. A new lease will not be - * available before the break period has expired, but the lease may be held for longer than the break - * period. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return - * A reactive response containing the remaining time in the broken lease in seconds. + * @param breakPeriodInSeconds An optional {@code Integer} representing the proposed duration of seconds that the + * lease should continue before it is broken, between 0 and 60 seconds. This break period is only used if it is + * shorter than the time remaining on the lease. If longer, the time remaining on the lease is used. A new lease + * will not be available before the break period has expired, but the lease may be held for longer than the break + * period. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @return A reactive response containing the remaining time in the broken lease in seconds. */ public Mono> breakLease(Integer breakPeriodInSeconds, ModifiedAccessConditions modifiedAccessConditions) { - return blobAsyncRawClient - .breakLease(breakPeriodInSeconds, modifiedAccessConditions) + return postProcessResponse(this.azureBlobStorage.blobs().breakLeaseWithRestResponseAsync(null, + null, null, breakPeriodInSeconds, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseTime())); } /** * ChangeLease changes the blob's lease ID. * - * @param leaseId - * The leaseId of the active lease on the blob. - * @param proposedID - * A {@code String} in any valid GUID format. - * - * @return - * A reactive response containing the new lease ID. + * @param leaseId The leaseId of the active lease on the blob. + * @param proposedID A {@code String} in any valid GUID format. + * @return A reactive response containing the new lease ID. */ public Mono> changeLease(String leaseId, String proposedID) { return this.changeLease(leaseId, proposedID, null); } /** - * ChangeLease changes the blob's lease ID. For more information, see the Azure Docs. - * - * @param leaseId - * The leaseId of the active lease on the blob. - * @param proposedID - * A {@code String} in any valid GUID format. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. + * ChangeLease changes the blob's lease ID. For more information, see the Azure + * Docs. * + * @param leaseId The leaseId of the active lease on the blob. + * @param proposedID A {@code String} in any valid GUID format. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. * @return A reactive response containing the new lease ID. */ public Mono> changeLease(String leaseId, String proposedID, ModifiedAccessConditions modifiedAccessConditions) { - return blobAsyncRawClient - .changeLease(leaseId, proposedID, modifiedAccessConditions) + return postProcessResponse(this.azureBlobStorage.blobs().changeLeaseWithRestResponseAsync(null, + null, leaseId, proposedID, null, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseId())); } /** - * Returns the sku name and account kind for the account. For more information, please see the Azure Docs. + * Returns the sku name and account kind for the account. For more information, please see the Azure Docs. * * @return a reactor response containing the sku name and account kind. */ // TODO (unknown): determine this return type public Mono> getAccountInfo() { - return blobAsyncRawClient - .getAccountInfo() + return postProcessResponse( + this.azureBlobStorage.blobs().getAccountInfoWithRestResponseAsync(null, null, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new StorageAccountInfo(rb.deserializedHeaders()))); } /** * Generates a user delegation SAS with the specified parameters * - * @param userDelegationKey - * The {@code UserDelegationKey} user delegation key for the SAS - * @param accountName - * The {@code String} account name for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * - * @return - * A string that represents the SAS token + * @param userDelegationKey The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName The {@code String} account name for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @return A string that represents the SAS token */ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, - BlobSASPermission permissions, OffsetDateTime expiryTime) { + BlobSASPermission permissions, OffsetDateTime expiryTime) { return this.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, null /* startTime */, null /* version */, null /*sasProtocol */, null /* ipRange */, null /* cacheControl */, null /*contentDisposition */, null /* contentEncoding */, null /* contentLanguage */, null /* contentType */); @@ -803,29 +806,19 @@ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, Str /** * Generates a user delegation SAS token with the specified parameters * - * @param userDelegationKey - * The {@code UserDelegationKey} user delegation key for the SAS - * @param accountName - * The {@code String} account name for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * - * @return - * A string that represents the SAS token + * @param userDelegationKey The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName The {@code String} account name for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @return A string that represents the SAS token */ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, - BlobSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, - SASProtocol sasProtocol, IPRange ipRange) { + BlobSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange) { return this.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, startTime, version, sasProtocol, ipRange, null /* cacheControl */, null /* contentDisposition */, null /* contentEncoding */, null /* contentLanguage */, null /* contentType */); @@ -834,40 +827,25 @@ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, Str /** * Generates a user delegation SAS token with the specified parameters * - * @param userDelegationKey - * The {@code UserDelegationKey} user delegation key for the SAS - * @param accountName - * The {@code String} account name for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * @param cacheControl - * An optional {@code String} cache-control header for the SAS. - * @param contentDisposition - * An optional {@code String} content-disposition header for the SAS. - * @param contentEncoding - * An optional {@code String} content-encoding header for the SAS. - * @param contentLanguage - * An optional {@code String} content-language header for the SAS. - * @param contentType - * An optional {@code String} content-type header for the SAS. - * - * @return - * A string that represents the SAS token + * @param userDelegationKey The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName The {@code String} account name for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @param cacheControl An optional {@code String} cache-control header for the SAS. + * @param contentDisposition An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage An optional {@code String} content-language header for the SAS. + * @param contentType An optional {@code String} content-type header for the SAS. + * @return A string that represents the SAS token */ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, - BlobSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, - SASProtocol sasProtocol, IPRange ipRange, String cacheControl, String contentDisposition, - String contentEncoding, String contentLanguage, String contentType) { + BlobSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange, String cacheControl, String contentDisposition, + String contentEncoding, String contentLanguage, String contentType) { ServiceSASSignatureValues serviceSASSignatureValues = new ServiceSASSignatureValues(version, sasProtocol, startTime, expiryTime, permissions == null ? null : permissions.toString(), ipRange, null /* identifier*/, @@ -883,13 +861,9 @@ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, Str /** * Generates a SAS token with the specified parameters * - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * - * @return - * A string that represents the SAS token + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @return A string that represents the SAS token */ public String generateSAS(BlobSASPermission permissions, OffsetDateTime expiryTime) { return this.generateSAS(null, permissions, expiryTime, null /* startTime */, /* identifier */ null /* @@ -900,11 +874,8 @@ public String generateSAS(BlobSASPermission permissions, OffsetDateTime expiryTi /** * Generates a SAS token with the specified parameters * - * @param identifier - * The {@code String} name of the access policy on the container this SAS references if any - * - * @return - * A string that represents the SAS token + * @param identifier The {@code String} name of the access policy on the container this SAS references if any + * @return A string that represents the SAS token */ public String generateSAS(String identifier) { return this.generateSAS(identifier, null /* permissions */, null /* expiryTime */, null /* startTime */, @@ -915,26 +886,17 @@ public String generateSAS(String identifier) { /** * Generates a SAS token with the specified parameters * - * @param identifier - * The {@code String} name of the access policy on the container this SAS references if any - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * - * @return - * A string that represents the SAS token + * @param identifier The {@code String} name of the access policy on the container this SAS references if any + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @return A string that represents the SAS token */ public String generateSAS(String identifier, BlobSASPermission permissions, OffsetDateTime expiryTime, - OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange) { + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange) { return this.generateSAS(identifier, permissions, expiryTime, startTime, version, sasProtocol, ipRange, null /* cacheControl */, null /* contentLanguage*/, null /* contentEncoding */, null /* contentLanguage */, null /* contentType */); @@ -943,44 +905,30 @@ public String generateSAS(String identifier, BlobSASPermission permissions, Offs /** * Generates a SAS token with the specified parameters * - * @param identifier - * The {@code String} name of the access policy on the container this SAS references if any - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * @param cacheControl - * An optional {@code String} cache-control header for the SAS. - * @param contentDisposition - * An optional {@code String} content-disposition header for the SAS. - * @param contentEncoding - * An optional {@code String} content-encoding header for the SAS. - * @param contentLanguage - * An optional {@code String} content-language header for the SAS. - * @param contentType - * An optional {@code String} content-type header for the SAS. - * - * @return - * A string that represents the SAS token + * @param identifier The {@code String} name of the access policy on the container this SAS references if any + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @param cacheControl An optional {@code String} cache-control header for the SAS. + * @param contentDisposition An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage An optional {@code String} content-language header for the SAS. + * @param contentType An optional {@code String} content-type header for the SAS. + * @return A string that represents the SAS token */ public String generateSAS(String identifier, BlobSASPermission permissions, OffsetDateTime expiryTime, - OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange, String cacheControl, - String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange, String cacheControl, + String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { ServiceSASSignatureValues serviceSASSignatureValues = new ServiceSASSignatureValues(version, sasProtocol, startTime, expiryTime, permissions == null ? null : permissions.toString(), ipRange, identifier, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); SharedKeyCredential sharedKeyCredential = - Utility.getSharedKeyCredential(this.blobAsyncRawClient.azureBlobStorage.httpPipeline()); + Utility.getSharedKeyCredential(this.azureBlobStorage.httpPipeline()); Utility.assertNotNull("sharedKeyCredential", sharedKeyCredential); @@ -996,10 +944,10 @@ public String generateSAS(String identifier, BlobSASPermission permissions, Offs * Sets serviceSASSignatureValues parameters dependent on the current blob type */ ServiceSASSignatureValues configureServiceSASSignatureValues(ServiceSASSignatureValues serviceSASSignatureValues, - String accountName) { + String accountName) { // Set canonical name - serviceSASSignatureValues.canonicalName(this.blobAsyncRawClient.azureBlobStorage.url(), accountName); + serviceSASSignatureValues.canonicalName(this.azureBlobStorage.url(), accountName); // Set snapshotId serviceSASSignatureValues.snapshotId(getSnapshotId()); @@ -1017,20 +965,18 @@ ServiceSASSignatureValues configureServiceSASSignatureValues(ServiceSASSignature /** * Gets the snapshotId for a blob resource * - * @return - * A string that represents the snapshotId of the snapshot blob + * @return A string that represents the snapshotId of the snapshot blob */ public String getSnapshotId() { - return this.blobAsyncRawClient.snapshot; + return this.snapshot; } /** * Determines if a blob is a snapshot * - * @return - * A boolean that indicates if a blob is a snapshot + * @return A boolean that indicates if a blob is a snapshot */ public boolean isSnapshot() { - return this.blobAsyncRawClient.snapshot != null; + return this.snapshot != null; } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncRawClient.java deleted file mode 100644 index 002ba761a3bf8..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobAsyncRawClient.java +++ /dev/null @@ -1,766 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.util.Context; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.AccessTier; -import com.azure.storage.blob.models.BlobAccessConditions; -import com.azure.storage.blob.models.BlobHTTPHeaders; -import com.azure.storage.blob.models.BlobRange; -import com.azure.storage.blob.models.BlobStartCopyFromURLHeaders; -import com.azure.storage.blob.models.BlobsAbortCopyFromURLResponse; -import com.azure.storage.blob.models.BlobsAcquireLeaseResponse; -import com.azure.storage.blob.models.BlobsBreakLeaseResponse; -import com.azure.storage.blob.models.BlobsChangeLeaseResponse; -import com.azure.storage.blob.models.BlobsCopyFromURLResponse; -import com.azure.storage.blob.models.BlobsCreateSnapshotResponse; -import com.azure.storage.blob.models.BlobsDeleteResponse; -import com.azure.storage.blob.models.BlobsGetAccountInfoResponse; -import com.azure.storage.blob.models.BlobsGetPropertiesResponse; -import com.azure.storage.blob.models.BlobsReleaseLeaseResponse; -import com.azure.storage.blob.models.BlobsRenewLeaseResponse; -import com.azure.storage.blob.models.BlobsSetHTTPHeadersResponse; -import com.azure.storage.blob.models.BlobsSetMetadataResponse; -import com.azure.storage.blob.models.BlobsSetTierResponse; -import com.azure.storage.blob.models.BlobsStartCopyFromURLResponse; -import com.azure.storage.blob.models.BlobsUndeleteResponse; -import com.azure.storage.blob.models.DeleteSnapshotsOptionType; -import com.azure.storage.blob.models.LeaseAccessConditions; -import com.azure.storage.blob.models.Metadata; -import com.azure.storage.blob.models.ModifiedAccessConditions; -import com.azure.storage.blob.models.ReliableDownloadOptions; -import com.azure.storage.blob.models.SourceModifiedAccessConditions; -import reactor.core.publisher.Mono; - -import java.net.URL; - -import static com.azure.storage.blob.Utility.postProcessResponse; - -/** - * Represents a URL to a blob of any type: block, append, or page. It may be obtained by direct construction or via the - * create method on a {@link ContainerAsyncClient} object. This class does not hold any state about a particular blob but is - * instead a convenient way of sending off appropriate requests to the resource on the service. Please refer to the - * Azure Docs for more information. - */ -class BlobAsyncRawClient { - - protected AzureBlobStorageImpl azureBlobStorage; - - final String snapshot; - - /** - * Creates a {@code BlobAsyncRawClient} object pointing to the account specified by the URL and using the provided pipeline to - * make HTTP requests.. - */ - BlobAsyncRawClient(AzureBlobStorageImpl azureBlobStorage, String snapshot) { - this.azureBlobStorage = azureBlobStorage; - this.snapshot = snapshot; - } - - /** - * Copies the data at the source URL to a blob. For more information, see the Azure Docs - * - * @param sourceURL - * The source URL to copy from. URLs outside of Azure may only be copied to block blobs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=start_copy "Sample code for BlobAsyncRawClient.startCopyFromURL")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=start_copy_helper "Helper for start_copy sample.")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono startCopyFromURL(URL sourceURL) { - return this.startCopyFromURL(sourceURL, null, null, null); - } - - /** - * Copies the data at the source URL to a blob. For more information, see the Azure Docs - * - * @param sourceURL - * The source URL to copy from. URLs outside of Azure may only be copied to block blobs. - * @param metadata - * {@link Metadata} - * @param sourceModifiedAccessConditions - * {@link ModifiedAccessConditions} against the source. Standard HTTP Access conditions related to the - * modification of data. ETag and LastModifiedTime are used to construct conditions related to when the blob - * was changed relative to the given request. The request will fail if the specified condition is not - * satisfied. - * @param destAccessConditions - * {@link BlobAccessConditions} against the destination. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=start_copy "Sample code for BlobAsyncRawClient.startCopyFromURL")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=start_copy_helper "Helper for start_copy sample.")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono startCopyFromURL(URL sourceURL, Metadata metadata, - ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - sourceModifiedAccessConditions = sourceModifiedAccessConditions == null - ? new ModifiedAccessConditions() : sourceModifiedAccessConditions; - destAccessConditions = destAccessConditions == null ? new BlobAccessConditions() : destAccessConditions; - - // We want to hide the SourceAccessConditions type from the user for consistency's sake, so we convert here. - SourceModifiedAccessConditions sourceConditions = new SourceModifiedAccessConditions() - .sourceIfModifiedSince(sourceModifiedAccessConditions.ifModifiedSince()) - .sourceIfUnmodifiedSince(sourceModifiedAccessConditions.ifUnmodifiedSince()) - .sourceIfMatch(sourceModifiedAccessConditions.ifMatch()) - .sourceIfNoneMatch(sourceModifiedAccessConditions.ifNoneMatch()); - - return postProcessResponse(this.azureBlobStorage.blobs().startCopyFromURLWithRestResponseAsync( - null, null, sourceURL, null, metadata, null, sourceConditions, - destAccessConditions.modifiedAccessConditions(), destAccessConditions.leaseAccessConditions(), Context.NONE)); - } - - /** - * Stops a pending copy that was previously started and leaves a destination blob with 0 length and metadata. For - * more information, see the Azure Docs. - * - * @param copyId - * The id of the copy operation to abort. Returned as the {@code copyId} field on the {@link - * BlobStartCopyFromURLHeaders} object. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=abort_copy "Sample code for BlobAsyncRawClient.abortCopyFromURL")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono abortCopyFromURL(String copyId) { - return this.abortCopyFromURL(copyId, null); - } - - /** - * Stops a pending copy that was previously started and leaves a destination blob with 0 length and metadata. For - * more information, see the Azure Docs. - * - * @param copyId - * The id of the copy operation to abort. Returned as the {@code copyId} field on the {@link - * BlobStartCopyFromURLHeaders} object. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=abort_copy "Sample code for BlobAsyncRawClient.abortCopyFromURL")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono abortCopyFromURL(String copyId, - LeaseAccessConditions leaseAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blobs().abortCopyFromURLWithRestResponseAsync( - null, null, copyId, null, null, leaseAccessConditions, Context.NONE)); - } - - /** - * Copies the data at the source URL to a blob and waits for the copy to complete before returning a response. - * For more information, see the Azure Docs - * - * @param copySource - * The source URL to copy from. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=sync_copy "Sample code for BlobAsyncRawClient.copyFromURL")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono syncCopyFromURL(URL copySource) { - return this.syncCopyFromURL(copySource, null, null, null); - } - - /** - * Copies the data at the source URL to a blob and waits for the copy to complete before returning a response. - * For more information, see the Azure Docs - * - * @param copySource - * The source URL to copy from. URLs outside of Azure may only be copied to block blobs. - * @param metadata - * {@link Metadata} - * @param sourceModifiedAccessConditions - * {@link ModifiedAccessConditions} against the source. Standard HTTP Access conditions related to the - * modification of data. ETag and LastModifiedTime are used to construct conditions related to when the blob - * was changed relative to the given request. The request will fail if the specified condition is not - * satisfied. - * @param destAccessConditions - * {@link BlobAccessConditions} against the destination. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=sync_copy "Sample code for BlobAsyncRawClient.copyFromURL")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono syncCopyFromURL(URL copySource, Metadata metadata, - ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - sourceModifiedAccessConditions = sourceModifiedAccessConditions == null - ? new ModifiedAccessConditions() : sourceModifiedAccessConditions; - destAccessConditions = destAccessConditions == null ? new BlobAccessConditions() : destAccessConditions; - - // We want to hide the SourceAccessConditions type from the user for consistency's sake, so we convert here. - SourceModifiedAccessConditions sourceConditions = new SourceModifiedAccessConditions() - .sourceIfModifiedSince(sourceModifiedAccessConditions.ifModifiedSince()) - .sourceIfUnmodifiedSince(sourceModifiedAccessConditions.ifUnmodifiedSince()) - .sourceIfMatch(sourceModifiedAccessConditions.ifMatch()) - .sourceIfNoneMatch(sourceModifiedAccessConditions.ifNoneMatch()); - - return postProcessResponse(this.azureBlobStorage.blobs().copyFromURLWithRestResponseAsync( - null, null, copySource, null, metadata, null, sourceConditions, - destAccessConditions.modifiedAccessConditions(), destAccessConditions.leaseAccessConditions(), Context.NONE)); - } - - /** - * Reads a range of bytes from a blob. The response also includes the blob's properties and metadata. For more - * information, see the Azure Docs. - *

- * Note that the response body has reliable download functionality built in, meaning that a failed download stream - * will be automatically retried. This behavior may be configured with {@link ReliableDownloadOptions}. - * - * @return Emits the successful response. - * @apiNote ## Sample Code \n [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=upload_download - * "Sample code for BlobAsyncRawClient.download")] \n For more samples, please see the [Samples - * file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono download() { - return this.download(null, null, false); - } - - /** - * Reads a range of bytes from a blob. The response also includes the blob's properties and metadata. For more - * information, see the Azure Docs. - *

- * Note that the response body has reliable download functionality built in, meaning that a failed download stream - * will be automatically retried. This behavior may be configured with {@link ReliableDownloadOptions}. - * - * @param range - * {@link BlobRange} - * @param accessConditions - * {@link BlobAccessConditions} - * @param rangeGetContentMD5 - * Whether the contentMD5 for the specified blob range should be returned. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=upload_download "Sample code for BlobAsyncRawClient.download")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono download(BlobRange range, BlobAccessConditions accessConditions, - boolean rangeGetContentMD5) { - range = range == null ? new BlobRange(0) : range; - Boolean getMD5 = rangeGetContentMD5 ? rangeGetContentMD5 : null; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - HTTPGetterInfo info = new HTTPGetterInfo() - .offset(range.offset()) - .count(range.count()) - .eTag(accessConditions.modifiedAccessConditions().ifMatch()); - - // TODO: range is BlobRange but expected as String - // TODO: figure out correct response - return postProcessResponse(this.azureBlobStorage.blobs().downloadWithRestResponseAsync( - null, null, snapshot, null, null, range.toHeaderValue(), getMD5, - null, null, null, null, - accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) - // Convert the autorest response to a DownloadAsyncResponse, which enable reliable download. - .map(response -> { - // If there wasn't an etag originally specified, lock on the one returned. - info.eTag(response.deserializedHeaders().eTag()); - return new DownloadAsyncResponse(response, info, - // In the event of a stream failure, make a new request to pick up where we left off. - newInfo -> - this.download(new BlobRange(newInfo.offset(), newInfo.count()), - new BlobAccessConditions().modifiedAccessConditions( - new ModifiedAccessConditions().ifMatch(info.eTag())), false)); - }); - } - - /** - * Deletes the specified blob or snapshot. Note that deleting a blob also deletes all its snapshots. For more - * information, see the Azure Docs. - * - * @return Emits the successful response. - * @apiNote ## Sample Code \n [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_delete - * "Sample code for BlobAsyncRawClient.delete")] \n For more samples, please see the [Samples - * file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono delete() { - return this.delete(null, null); - } - - /** - * Deletes the specified blob or snapshot. Note that deleting a blob also deletes all its snapshots. For more - * information, see the Azure Docs. - * - * @param deleteBlobSnapshotOptions - * Specifies the behavior for deleting the snapshots on this blob. {@code Include} will delete the base blob - * and all snapshots. {@code Only} will delete only the snapshots. If a snapshot is being deleted, you must - * pass null. - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_delete "Sample code for BlobAsyncRawClient.delete")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono delete(DeleteSnapshotsOptionType deleteBlobSnapshotOptions, - BlobAccessConditions accessConditions) { - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blobs().deleteWithRestResponseAsync( - null, null, snapshot, null, null, deleteBlobSnapshotOptions, - null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), - Context.NONE)); - } - - /** - * Returns the blob's metadata and properties. For more information, see the Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=properties_metadata "Sample code for BlobAsyncRawClient.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getProperties() { - return this.getProperties(null); - } - - /** - * Returns the blob's metadata and properties. For more information, see the Azure Docs. - * - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=properties_metadata "Sample code for BlobAsyncRawClient.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getProperties(BlobAccessConditions accessConditions) { - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blobs().getPropertiesWithRestResponseAsync( - null, null, snapshot, null, null, null, - null, null, null, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Changes a blob's HTTP header properties. For more information, see the Azure - * Docs. - * - * @param headers - * {@link BlobHTTPHeaders} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=properties_metadata "Sample code for BlobAsyncRawClient.setHTTPHeaders")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setHTTPHeaders(BlobHTTPHeaders headers) { - return this.setHTTPHeaders(headers, null); - } - - /** - * Changes a blob's HTTP header properties. For more information, see the Azure Docs. - * - * @param headers - * {@link BlobHTTPHeaders} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=properties_metadata "Sample code for BlobAsyncRawClient.setHTTPHeaders")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setHTTPHeaders(BlobHTTPHeaders headers, - BlobAccessConditions accessConditions) { - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blobs().setHTTPHeadersWithRestResponseAsync( - null, null, null, null, headers, - accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Changes a blob's metadata. For more information, see the Azure Docs. - * - * @param metadata - * {@link Metadata} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=properties_metadata "Sample code for BlobAsyncRawClient.setMetadata")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setMetadata(Metadata metadata) { - return this.setMetadata(metadata, null); - } - - /** - * Changes a blob's metadata. For more information, see the Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=properties_metadata "Sample code for BlobAsyncRawClient.setMetadata")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setMetadata(Metadata metadata, BlobAccessConditions accessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blobs().setMetadataWithRestResponseAsync( - null, null, null, metadata, null, null, - null, null, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Creates a read-only snapshot of a blob. For more information, see the Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=snapshot "Sample code for BlobAsyncRawClient.createSnapshot")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono createSnapshot() { - return this.createSnapshot(null, null); - } - - /** - * Creates a read-only snapshot of a blob. For more information, see the Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=snapshot "Sample code for BlobAsyncRawClient.createSnapshot")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono createSnapshot(Metadata metadata, BlobAccessConditions accessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blobs().createSnapshotWithRestResponseAsync( - null, null, null, metadata, null, null, - null, null, accessConditions.modifiedAccessConditions(), - accessConditions.leaseAccessConditions(), Context.NONE)); - } - - /** - * Sets the tier on a blob. The operation is allowed on a page blob in a premium storage account or a block blob in - * a blob storage or GPV2 account. A premium page blob's tier determines the allowed size, IOPS, and bandwidth of - * the blob. A block blob's tier determines the Hot/Cool/Archive storage type. This does not update the blob's etag. - *

- * For detailed information about block blob level tiering see the Azure Docs. - * - * @param tier - * The new tier for the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=tier "Sample code for BlobAsyncRawClient.setTier")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setTier(AccessTier tier) { - return this.setTier(tier, null); - } - - /** - * Sets the tier on a blob. The operation is allowed on a page blob in a premium storage account or a block blob in - * a blob storage or GPV2 account. A premium page blob's tier determines the allowed size, IOPS, and bandwidth of - * the blob. A block blob's tier determines the Hot/Cool/Archive storage type. This does not update the blob's etag. - *

- * For detailed information about block blob level tiering see the Azure Docs. - * - * @param tier - * The new tier for the blob. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=tier "Sample code for BlobAsyncRawClient.setTier")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setTier(AccessTier tier, LeaseAccessConditions leaseAccessConditions) { - Utility.assertNotNull("tier", tier); - - return postProcessResponse(this.azureBlobStorage.blobs().setTierWithRestResponseAsync( - null, null, tier, null, null, leaseAccessConditions, Context.NONE)); - } - - /** - * Undelete restores the content and metadata of a soft-deleted blob and/or any associated soft-deleted snapshots. - * For more information, see the Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=undelete "Sample code for BlobAsyncRawClient.undelete")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono undelete() { - return postProcessResponse(this.azureBlobStorage.blobs().undeleteWithRestResponseAsync(null, - null, Context.NONE)); - } - - /** - * Acquires a lease on the blob for write and delete operations. The lease duration must be between 15 to 60 - * seconds, or infinite (-1). For more information, see the Azure Docs. - * - * @param proposedId - * A {@code String} in any valid GUID format. May be null. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that - * never expires. A non-infinite lease can be between 15 and 60 seconds. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono acquireLease(String proposedId, int duration) { - return this.acquireLease(proposedId, duration, null); - } - - /** - * Acquires a lease on the blob for write and delete operations. The lease duration must be between 15 to 60 - * seconds, or infinite (-1). For more information, see the Azure Docs. - * - * @param proposedID - * A {@code String} in any valid GUID format. May be null. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that - * never expires. A non-infinite lease can be between 15 and 60 seconds. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code duration} is outside the bounds of 15 to 60 or isn't -1. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono acquireLease(String proposedID, int duration, - ModifiedAccessConditions modifiedAccessConditions) { - if (!(duration == -1 || (duration >= 15 && duration <= 60))) { - // Throwing is preferred to Mono.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("Duration must be -1 or between 15 and 60."); - } - - return postProcessResponse(this.azureBlobStorage.blobs().acquireLeaseWithRestResponseAsync( - null, null, null, duration, proposedID, null, - modifiedAccessConditions, Context.NONE)); - } - - /** - * Renews the blob's previously-acquired lease. For more information, see the Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.renewLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono renewLease(String leaseID) { - return this.renewLease(leaseID, null); - } - - /** - * Renews the blob's previously-acquired lease. For more information, see the Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the blob. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.renewLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono renewLease(String leaseID, ModifiedAccessConditions modifiedAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blobs().renewLeaseWithRestResponseAsync(null, - null, leaseID, null, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * Releases the blob's previously-acquired lease. For more information, see the Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.releaseLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono releaseLease(String leaseID) { - return this.releaseLease(leaseID, null); - } - - /** - * Releases the blob's previously-acquired lease. For more information, see the Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the blob. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.releaseLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono releaseLease(String leaseID, - ModifiedAccessConditions modifiedAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blobs().releaseLeaseWithRestResponseAsync(null, - null, leaseID, null, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1) constant - * to break a fixed-duration lease when it expires or an infinite lease immediately. For more information, see the - * Azure Docs. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.breakLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - * - * @return - * Emits the successful response. - */ - public Mono breakLease() { - return this.breakLease(null, null); - } - - /** - * BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1) constant - * to break a fixed-duration lease when it expires or an infinite lease immediately. For more information, see the - * Azure Docs. - * - * @param breakPeriodInSeconds - * An optional {@code Integer} representing the proposed duration of seconds that the lease should continue - * before it is broken, between 0 and 60 seconds. This break period is only used if it is shorter than the - * time remaining on the lease. If longer, the time remaining on the lease is used. A new lease will not be - * available before the break period has expired, but the lease may be held for longer than the break - * period. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.breakLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono breakLease(Integer breakPeriodInSeconds, - ModifiedAccessConditions modifiedAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blobs().breakLeaseWithRestResponseAsync(null, - null, null, breakPeriodInSeconds, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * ChangeLease changes the blob's lease ID. For more information, see the Azure Docs. - * - * @param leaseId - * The leaseId of the active lease on the blob. - * @param proposedID - * A {@code String} in any valid GUID format. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.changeLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono changeLease(String leaseId, String proposedID) { - return this.changeLease(leaseId, proposedID, null); - } - - /** - * ChangeLease changes the blob's lease ID. For more information, see the Azure Docs. - * - * @param leaseId - * The leaseId of the active lease on the blob. - * @param proposedID - * A {@code String} in any valid GUID format. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blob_lease "Sample code for BlobAsyncRawClient.changeLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono changeLease(String leaseId, String proposedID, - ModifiedAccessConditions modifiedAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blobs().changeLeaseWithRestResponseAsync(null, - null, leaseId, proposedID, null, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for BlobAsyncRawClient.getAccountInfo")] \n - * For more samples, please see the [Samples file](https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getAccountInfo() { - return postProcessResponse( - this.azureBlobStorage.blobs().getAccountInfoWithRestResponseAsync(null, null, Context.NONE)); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClient.java index 21a2212713f5c..a937b8ca47613 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClient.java @@ -27,30 +27,30 @@ import java.time.OffsetDateTime; /** - * Client to a blob of any type: block, append, or page. It may only be instantiated through a {@link BlobClientBuilder} or via - * the method {@link ContainerClient#getBlobClient(String)}. This class does not hold any state about a particular - * blob, but is instead a convenient way of sending appropriate requests to the resource on the service. + * Client to a blob of any type: block, append, or page. It may only be instantiated through a {@link BlobClientBuilder} + * or via the method {@link ContainerClient#getBlobClient(String)}. This class does not hold any state about a + * particular blob, but is instead a convenient way of sending appropriate requests to the resource on the service. * *

* This client offers the ability to download blobs. Note that uploading data is specific to each type of blob. Please * refer to the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link AppendBlobClient} for upload options. This - * client can be converted into one of these clients easily through the methods {@link #asBlockBlobClient}, {@link #asPageBlobClient}, - * and {@link #asAppendBlobClient}. + * client can be converted into one of these clients easily through the methods {@link #asBlockBlobClient}, {@link + * #asPageBlobClient}, and {@link #asAppendBlobClient}. * *

- * This client contains operations on a blob. Operations on a container are available on {@link ContainerClient}, - * and operations on the service are available on {@link StorageClient}. + * This client contains operations on a blob. Operations on a container are available on {@link ContainerClient}, and + * operations on the service are available on {@link StorageClient}. * *

- * Please refer to the Azure Docs - * for more information. + * Please refer to the Azure + * Docs for more information. */ public class BlobClient { - private static final int BLOB_DEFAULT_DOWNLOAD_BLOCK_SIZE = 4 * Constants.MB; private final BlobAsyncClient blobAsyncClient; /** * Package-private constructor for use by {@link BlobClientBuilder}. + * * @param blobAsyncClient the async blob client */ BlobClient(BlobAsyncClient blobAsyncClient) { @@ -58,45 +58,41 @@ public class BlobClient { } /** - * Creates a new {@link BlockBlobClient} to this resource, maintaining configurations. Only do this for blobs - * that are known to be block blobs. + * Creates a new {@link BlockBlobClient} to this resource, maintaining configurations. Only do this for blobs that + * are known to be block blobs. * - * @return - * A {@link BlockBlobClient} to this resource. + * @return A {@link BlockBlobClient} to this resource. */ public BlockBlobClient asBlockBlobClient() { return new BlockBlobClient(blobAsyncClient.asBlockBlobAsyncClient()); } /** - * Creates a new {@link AppendBlobClient} to this resource, maintaining configurations. Only do this for blobs - * that are known to be append blobs. + * Creates a new {@link AppendBlobClient} to this resource, maintaining configurations. Only do this for blobs that + * are known to be append blobs. * - * @return - * A {@link AppendBlobClient} to this resource. + * @return A {@link AppendBlobClient} to this resource. */ public AppendBlobClient asAppendBlobClient() { return new AppendBlobClient(blobAsyncClient.asAppendBlobAsyncClient()); } /** - * Creates a new {@link PageBlobClient} to this resource, maintaining configurations. Only do this for blobs - * that are known to be page blobs. + * Creates a new {@link PageBlobClient} to this resource, maintaining configurations. Only do this for blobs that + * are known to be page blobs. * - * @return - * A {@link PageBlobClient} to this resource. + * @return A {@link PageBlobClient} to this resource. */ public PageBlobClient asPageBlobClient() { return new PageBlobClient(blobAsyncClient.asPageBlobAsyncClient()); } /** - * Initializes a {@link ContainerClient} object pointing to the container this blob is in. This method does - * not create a container. It simply constructs the URL to the container and offers access to methods relevant to + * Initializes a {@link ContainerClient} object pointing to the container this blob is in. This method does not + * create a container. It simply constructs the URL to the container and offers access to methods relevant to * containers. * - * @return - * A {@link ContainerClient} object pointing to the container containing the blob + * @return A {@link ContainerClient} object pointing to the container containing the blob */ public ContainerClient getContainerClient() { return new ContainerClient(blobAsyncClient.getContainerAsyncClient()); @@ -104,6 +100,7 @@ public ContainerClient getContainerClient() { /** * Gets the URL of the blob represented by this client. + * * @return the URL. */ public URL getBlobUrl() { @@ -115,9 +112,7 @@ public URL getBlobUrl() { *

* * @return An InputStream object that represents the stream to use for reading from the blob. - * - * @throws StorageException - * If a storage service error occurred. + * @throws StorageException If a storage service error occurred. */ public final BlobInputStream openInputStream() { return openInputStream(new BlobRange(0), null); @@ -127,15 +122,11 @@ public final BlobInputStream openInputStream() { * Opens a blob input stream to download the specified range of the blob. *

* - * @param range - * {@link BlobRange} - * @param accessConditions - * An {@link BlobAccessConditions} object that represents the access conditions for the blob. - * + * @param range {@link BlobRange} + * @param accessConditions An {@link BlobAccessConditions} object that represents the access conditions for the + * blob. * @return An InputStream object that represents the stream to use for reading from the blob. - * - * @throws StorageException - * If a storage service error occurred. + * @throws StorageException If a storage service error occurred. */ public final BlobInputStream openInputStream(BlobRange range, BlobAccessConditions accessConditions) { return new BlobInputStream(blobAsyncClient, range.offset(), range.count(), accessConditions); @@ -153,10 +144,8 @@ public Response exists() { /** * Gets if the container this client represents exists in the cloud. * - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * @return - * true if the container exists, false if it doesn't + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return true if the container exists, false if it doesn't */ public Response exists(Duration timeout) { Mono> response = blobAsyncClient.exists(); @@ -168,11 +157,8 @@ public Response exists(Duration timeout) { * Copies the data at the source URL to a blob. For more information, see the * Azure Docs * - * @param sourceURL - * The source URL to copy from. URLs outside of Azure may only be copied to block blobs. - * - * @return - * The copy ID for the long running operation. + * @param sourceURL The source URL to copy from. URLs outside of Azure may only be copied to block blobs. + * @return The copy ID for the long running operation. */ public Response startCopyFromURL(URL sourceURL) { return this.startCopyFromURL(sourceURL, null, null, null, null); @@ -182,26 +168,19 @@ public Response startCopyFromURL(URL sourceURL) { * Copies the data at the source URL to a blob. For more information, see the * Azure Docs * - * @param sourceURL - * The source URL to copy from. URLs outside of Azure may only be copied to block blobs. - * @param metadata - * {@link Metadata} - * @param sourceModifiedAccessConditions - * {@link ModifiedAccessConditions} against the source. Standard HTTP Access conditions related to the - * modification of data. ETag and LastModifiedTime are used to construct conditions related to when the blob - * was changed relative to the given request. The request will fail if the specified condition is not - * satisfied. - * @param destAccessConditions - * {@link BlobAccessConditions} against the destination. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The copy ID for the long running operation. + * @param sourceURL The source URL to copy from. URLs outside of Azure may only be copied to block blobs. + * @param metadata {@link Metadata} + * @param sourceModifiedAccessConditions {@link ModifiedAccessConditions} against the source. Standard HTTP Access + * conditions related to the modification of data. ETag and LastModifiedTime are used to construct conditions + * related to when the blob was changed relative to the given request. The request will fail if the specified + * condition is not satisfied. + * @param destAccessConditions {@link BlobAccessConditions} against the destination. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The copy ID for the long running operation. */ public Response startCopyFromURL(URL sourceURL, Metadata metadata, - ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions, - Duration timeout) { + ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions, + Duration timeout) { Mono> response = blobAsyncClient .startCopyFromURL(sourceURL, metadata, sourceModifiedAccessConditions, destAccessConditions); @@ -211,9 +190,8 @@ public Response startCopyFromURL(URL sourceURL, Metadata metadata, /** * Stops a pending copy that was previously started and leaves a destination blob with 0 length and metadata. * - * @param copyId - * The id of the copy operation to abort. Returned as the {@code copyId} field on the {@link - * BlobStartCopyFromURLHeaders} object. + * @param copyId The id of the copy operation to abort. Returned as the {@code copyId} field on the {@link + * BlobStartCopyFromURLHeaders} object. * @return A response containing status code and HTTP headers. */ public VoidResponse abortCopyFromURL(String copyId) { @@ -223,14 +201,11 @@ public VoidResponse abortCopyFromURL(String copyId) { /** * Stops a pending copy that was previously started and leaves a destination blob with 0 length and metadata. * - * @param copyId - * The id of the copy operation to abort. Returned as the {@code copyId} field on the {@link - * BlobStartCopyFromURLHeaders} object. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param copyId The id of the copy operation to abort. Returned as the {@code copyId} field on the {@link + * BlobStartCopyFromURLHeaders} object. + * @param leaseAccessConditions By setting lease access conditions, requests will fail if the provided lease does + * not match the active lease on the blob. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers. */ public VoidResponse abortCopyFromURL(String copyId, LeaseAccessConditions leaseAccessConditions, Duration timeout) { @@ -243,11 +218,8 @@ public VoidResponse abortCopyFromURL(String copyId, LeaseAccessConditions leaseA /** * Copies the data at the source URL to a blob and waits for the copy to complete before returning a response. * - * @param copySource - * The source URL to copy from. - * - * @return - * The copy ID for the long running operation. + * @param copySource The source URL to copy from. + * @return The copy ID for the long running operation. */ public Response copyFromURL(URL copySource) { return this.copyFromURL(copySource, null, null, null, null); @@ -256,26 +228,19 @@ public Response copyFromURL(URL copySource) { /** * Copies the data at the source URL to a blob and waits for the copy to complete before returning a response. * - * @param copySource - * The source URL to copy from. URLs outside of Azure may only be copied to block blobs. - * @param metadata - * {@link Metadata} - * @param sourceModifiedAccessConditions - * {@link ModifiedAccessConditions} against the source. Standard HTTP Access conditions related to the - * modification of data. ETag and LastModifiedTime are used to construct conditions related to when the blob - * was changed relative to the given request. The request will fail if the specified condition is not - * satisfied. - * @param destAccessConditions - * {@link BlobAccessConditions} against the destination. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The copy ID for the long running operation. + * @param copySource The source URL to copy from. URLs outside of Azure may only be copied to block blobs. + * @param metadata {@link Metadata} + * @param sourceModifiedAccessConditions {@link ModifiedAccessConditions} against the source. Standard HTTP Access + * conditions related to the modification of data. ETag and LastModifiedTime are used to construct conditions + * related to when the blob was changed relative to the given request. The request will fail if the specified + * condition is not satisfied. + * @param destAccessConditions {@link BlobAccessConditions} against the destination. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The copy ID for the long running operation. */ public Response copyFromURL(URL copySource, Metadata metadata, - ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions, - Duration timeout) { + ModifiedAccessConditions sourceModifiedAccessConditions, BlobAccessConditions destAccessConditions, + Duration timeout) { Mono> response = blobAsyncClient .copyFromURL(copySource, metadata, sourceModifiedAccessConditions, destAccessConditions); @@ -286,8 +251,7 @@ public Response copyFromURL(URL copySource, Metadata metadata, * Downloads the entire blob into an output stream. Uploading data must be done from the {@link BlockBlobClient}, * {@link PageBlobClient}, or {@link AppendBlobClient}. * - * @param stream - * A non-null {@link OutputStream} instance where the downloaded data will be written. + * @param stream A non-null {@link OutputStream} instance where the downloaded data will be written. * @return A response containing status code and HTTP headers. * @throws UncheckedIOException If an I/O error occurs. */ @@ -296,23 +260,17 @@ public VoidResponse download(OutputStream stream) { } /** - * Downloads a range of bytes from a blob into an output stream. Uploading data must be done from the {@link BlockBlobClient}, - * {@link PageBlobClient}, or {@link AppendBlobClient}. + * Downloads a range of bytes from a blob into an output stream. Uploading data must be done from the {@link + * BlockBlobClient}, {@link PageBlobClient}, or {@link AppendBlobClient}. * - * @param stream - * A non-null {@link OutputStream} instance where the downloaded data will be written. + * @param stream A non-null {@link OutputStream} instance where the downloaded data will be written. * @param options {@link ReliableDownloadOptions} - * @param range - * {@link BlobRange} - * @param accessConditions - * {@link BlobAccessConditions} - * @param rangeGetContentMD5 - * Whether the contentMD5 for the specified blob range should be returned. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param range {@link BlobRange} + * @param accessConditions {@link BlobAccessConditions} + * @param rangeGetContentMD5 Whether the contentMD5 for the specified blob range should be returned. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers. * @throws UncheckedIOException If an I/O error occurs. - * */ public VoidResponse download(OutputStream stream, ReliableDownloadOptions options, BlobRange range, BlobAccessConditions accessConditions, boolean rangeGetContentMD5, Duration timeout) { @@ -334,37 +292,32 @@ public VoidResponse download(OutputStream stream, ReliableDownloadOptions option /** * Downloads the entire blob into a file specified by the path. The file will be created if it doesn't exist. - * Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link AppendBlobClient}. + * Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link + * AppendBlobClient}. * - * @param filePath - * A non-null {@link OutputStream} instance where the downloaded data will be written. + * @param filePath A non-null {@link OutputStream} instance where the downloaded data will be written. * @throws IOException If an I/O error occurs */ public void downloadToFile(String filePath) throws IOException { - this.downloadToFile(filePath, null, null, BLOB_DEFAULT_DOWNLOAD_BLOCK_SIZE, null, false, null); + blobAsyncClient.downloadToFile(filePath); } /** * Downloads a range of bytes blob into a file specified by the path. The file will be created if it doesn't exist. - * Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link AppendBlobClient}. + * Uploading data must be done from the {@link BlockBlobClient}, {@link PageBlobClient}, or {@link + * AppendBlobClient}. * - * @param filePath - * A non-null {@link OutputStream} instance where the downloaded data will be written. + * @param filePath A non-null {@link OutputStream} instance where the downloaded data will be written. * @param options {@link ReliableDownloadOptions} - * @param range - * {@link BlobRange} - * @param blockSize - * the size of a chunk to download at a time, in bytes - * @param accessConditions - * {@link BlobAccessConditions} - * @param rangeGetContentMD5 - * Whether the contentMD5 for the specified blob range should be returned. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param range {@link BlobRange} + * @param blockSize the size of a chunk to download at a time, in bytes + * @param accessConditions {@link BlobAccessConditions} + * @param rangeGetContentMD5 Whether the contentMD5 for the specified blob range should be returned. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @throws IOException If an I/O error occurs */ public void downloadToFile(String filePath, ReliableDownloadOptions options, BlobRange range, Integer blockSize, - BlobAccessConditions accessConditions, boolean rangeGetContentMD5, Duration timeout) throws IOException { + BlobAccessConditions accessConditions, boolean rangeGetContentMD5, Duration timeout) throws IOException { Mono download = blobAsyncClient.downloadToFile(filePath, range, blockSize, accessConditions, rangeGetContentMD5, options); try { @@ -386,19 +339,15 @@ public VoidResponse delete() { /** * Deletes the specified blob or snapshot. Note that deleting a blob also deletes all its snapshots. * - * @param deleteBlobSnapshotOptions - * Specifies the behavior for deleting the snapshots on this blob. {@code Include} will delete the base blob - * and all snapshots. {@code Only} will delete only the snapshots. If a snapshot is being deleted, you must - * pass null. - * @param accessConditions - * {@link BlobAccessConditions} - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * + * @param deleteBlobSnapshotOptions Specifies the behavior for deleting the snapshots on this blob. {@code Include} + * will delete the base blob and all snapshots. {@code Only} will delete only the snapshots. If a snapshot is being + * deleted, you must pass null. + * @param accessConditions {@link BlobAccessConditions} + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers. */ public VoidResponse delete(DeleteSnapshotsOptionType deleteBlobSnapshotOptions, - BlobAccessConditions accessConditions, Duration timeout) { + BlobAccessConditions accessConditions, Duration timeout) { Mono response = blobAsyncClient .delete(deleteBlobSnapshotOptions, accessConditions); @@ -408,8 +357,7 @@ public VoidResponse delete(DeleteSnapshotsOptionType deleteBlobSnapshotOptions, /** * Returns the blob's metadata and properties. * - * @return - * The blob properties and metadata. + * @return The blob properties and metadata. */ public Response getProperties() { return this.getProperties(null, null); @@ -418,13 +366,9 @@ public Response getProperties() { /** * Returns the blob's metadata and properties. * - * @param accessConditions - * {@link BlobAccessConditions} - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The blob properties and metadata. + * @param accessConditions {@link BlobAccessConditions} + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The blob properties and metadata. */ public Response getProperties(BlobAccessConditions accessConditions, Duration timeout) { Mono> response = blobAsyncClient @@ -434,13 +378,12 @@ public Response getProperties(BlobAccessConditions accessConditi } /** - * Changes a blob's HTTP header properties. if only one HTTP header is updated, the - * others will all be erased. In order to preserve existing values, they must be - * passed alongside the header being changed. For more information, see the + * Changes a blob's HTTP header properties. if only one HTTP header is updated, the others will all be erased. In + * order to preserve existing values, they must be passed alongside the header being changed. For more information, + * see the * Azure Docs. * - * @param headers - * {@link BlobHTTPHeaders} + * @param headers {@link BlobHTTPHeaders} * @return A response containing status code and HTTP headers. */ public VoidResponse setHTTPHeaders(BlobHTTPHeaders headers) { @@ -448,21 +391,18 @@ public VoidResponse setHTTPHeaders(BlobHTTPHeaders headers) { } /** - * Changes a blob's HTTP header properties. if only one HTTP header is updated, the - * others will all be erased. In order to preserve existing values, they must be - * passed alongside the header being changed. For more information, see the + * Changes a blob's HTTP header properties. if only one HTTP header is updated, the others will all be erased. In + * order to preserve existing values, they must be passed alongside the header being changed. For more information, + * see the * Azure Docs. * - * @param headers - * {@link BlobHTTPHeaders} - * @param accessConditions - * {@link BlobAccessConditions} - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param headers {@link BlobHTTPHeaders} + * @param accessConditions {@link BlobAccessConditions} + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers. */ public VoidResponse setHTTPHeaders(BlobHTTPHeaders headers, BlobAccessConditions accessConditions, - Duration timeout) { + Duration timeout) { Mono response = blobAsyncClient .setHTTPHeaders(headers, accessConditions); @@ -470,12 +410,11 @@ public VoidResponse setHTTPHeaders(BlobHTTPHeaders headers, BlobAccessConditions } /** - * Changes a blob's metadata. The specified metadata in this method will replace existing - * metadata. If old values must be preserved, they must be downloaded and included in the - * call to this method. For more information, see the Azure Docs. + * Changes a blob's metadata. The specified metadata in this method will replace existing metadata. If old values + * must be preserved, they must be downloaded and included in the call to this method. For more information, see the + * Azure Docs. * - * @param metadata - * {@link Metadata} + * @param metadata {@link Metadata} * @return A response containing status code and HTTP headers. */ public VoidResponse setMetadata(Metadata metadata) { @@ -483,16 +422,13 @@ public VoidResponse setMetadata(Metadata metadata) { } /** - * Changes a blob's metadata. The specified metadata in this method will replace existing - * metadata. If old values must be preserved, they must be downloaded and included in the - * call to this method. For more information, see the Azure Docs. + * Changes a blob's metadata. The specified metadata in this method will replace existing metadata. If old values + * must be preserved, they must be downloaded and included in the call to this method. For more information, see the + * Azure Docs. * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param metadata {@link Metadata} + * @param accessConditions {@link BlobAccessConditions} + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers. */ public VoidResponse setMetadata(Metadata metadata, BlobAccessConditions accessConditions, Duration timeout) { @@ -505,8 +441,7 @@ public VoidResponse setMetadata(Metadata metadata, BlobAccessConditions accessCo /** * Creates a read-only snapshot of a blob. * - * @return - * The ID of the new snapshot. + * @return The ID of the new snapshot. */ public Response createSnapshot() { return this.createSnapshot(null, null, null); @@ -515,15 +450,10 @@ public Response createSnapshot() { /** * Creates a read-only snapshot of a blob. * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The ID of the new snapshot. + * @param metadata {@link Metadata} + * @param accessConditions {@link BlobAccessConditions} + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The ID of the new snapshot. */ public Response createSnapshot(Metadata metadata, BlobAccessConditions accessConditions, Duration timeout) { Mono> response = blobAsyncClient @@ -535,10 +465,10 @@ public Response createSnapshot(Metadata metadata, BlobAccessConditions a /** * Sets the tier on a blob. The operation is allowed on a page blob in a premium storage account or a block blob in * a blob storage or GPV2 account. A premium page blob's tier determines the allowed size, IOPS, and bandwidth of - * the blob. A block blob's tier determines the Hot/Cool/Archive storage type. This does not update the blob's etag. + * the blob. A block blob's tier determines the Hot/Cool/Archive storage type. This does not update the blob's + * etag. * - * @param tier - * The new tier for the blob. + * @param tier The new tier for the blob. * @return A response containing status code and HTTP headers. */ public VoidResponse setTier(AccessTier tier) { @@ -548,15 +478,13 @@ public VoidResponse setTier(AccessTier tier) { /** * Sets the tier on a blob. The operation is allowed on a page blob in a premium storage account or a block blob in * a blob storage or GPV2 account. A premium page blob's tier determines the allowed size, IOPS, and bandwidth of - * the blob. A block blob's tier determines the Hot/Cool/Archive storage type. This does not update the blob's etag. - * - * @param tier - * The new tier for the blob. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * the blob. A block blob's tier determines the Hot/Cool/Archive storage type. This does not update the blob's + * etag. + * + * @param tier The new tier for the blob. + * @param leaseAccessConditions By setting lease access conditions, requests will fail if the provided lease does + * not match the active lease on the blob. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers. */ public VoidResponse setTier(AccessTier tier, LeaseAccessConditions leaseAccessConditions, Duration timeout) { @@ -578,8 +506,7 @@ public VoidResponse undelete() { /** * Undelete restores the content and metadata of a soft-deleted blob and/or any associated soft-deleted snapshots. * - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers. */ public VoidResponse undelete(Duration timeout) { @@ -593,14 +520,10 @@ public VoidResponse undelete(Duration timeout) { * Acquires a lease on the blob for write and delete operations. The lease duration must be between 15 to 60 * seconds, or infinite (-1). * - * @param proposedId - * A {@code String} in any valid GUID format. May be null. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that - * never expires. A non-infinite lease can be between 15 and 60 seconds. - * - * @return - * The lease ID. + * @param proposedId A {@code String} in any valid GUID format. May be null. + * @param duration The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. A + * non-infinite lease can be between 15 and 60 seconds. + * @return The lease ID. */ public Response acquireLease(String proposedId, int duration) { return this.acquireLease(proposedId, duration, null, null); @@ -610,23 +533,17 @@ public Response acquireLease(String proposedId, int duration) { * Acquires a lease on the blob for write and delete operations. The lease duration must be between 15 to 60 * seconds, or infinite (-1). * - * @param proposedID - * A {@code String} in any valid GUID format. May be null. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that - * never expires. A non-infinite lease can be between 15 and 60 seconds. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The lease ID. + * @param proposedID A {@code String} in any valid GUID format. May be null. + * @param duration The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. A + * non-infinite lease can be between 15 and 60 seconds. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The lease ID. */ public Response acquireLease(String proposedID, int duration, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { + ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { Mono> response = blobAsyncClient .acquireLease(proposedID, duration, modifiedAccessConditions); @@ -636,11 +553,8 @@ public Response acquireLease(String proposedID, int duration, /** * Renews the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * - * @return - * The renewed lease ID. + * @param leaseID The leaseId of the active lease on the blob. + * @return The renewed lease ID. */ public Response renewLease(String leaseID) { return this.renewLease(leaseID, null, null); @@ -649,20 +563,15 @@ public Response renewLease(String leaseID) { /** * Renews the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The renewed lease ID. + * @param leaseID The leaseId of the active lease on the blob. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The renewed lease ID. */ public Response renewLease(String leaseID, ModifiedAccessConditions modifiedAccessConditions, - Duration timeout) { + Duration timeout) { Mono> response = blobAsyncClient .renewLease(leaseID, modifiedAccessConditions); @@ -672,8 +581,7 @@ public Response renewLease(String leaseID, ModifiedAccessConditions modi /** * Releases the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. + * @param leaseID The leaseId of the active lease on the blob. * @return A response containing status code and HTTP headers. */ public VoidResponse releaseLease(String leaseID) { @@ -683,18 +591,15 @@ public VoidResponse releaseLease(String leaseID) { /** * Releases the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param leaseID The leaseId of the active lease on the blob. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers. */ public VoidResponse releaseLease(String leaseID, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { + ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { Mono response = blobAsyncClient .releaseLease(leaseID, modifiedAccessConditions); @@ -705,8 +610,7 @@ public VoidResponse releaseLease(String leaseID, * BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1) constant * to break a fixed-duration lease when it expires or an infinite lease immediately. * - * @return - * The remaining time in the broken lease in seconds. + * @return The remaining time in the broken lease in seconds. */ public Response breakLease() { return this.breakLease(null, null, null); @@ -716,24 +620,19 @@ public Response breakLease() { * BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1) constant * to break a fixed-duration lease when it expires or an infinite lease immediately. * - * @param breakPeriodInSeconds - * An optional {@code Integer} representing the proposed duration of seconds that the lease should continue - * before it is broken, between 0 and 60 seconds. This break period is only used if it is shorter than the - * time remaining on the lease. If longer, the time remaining on the lease is used. A new lease will not be - * available before the break period has expired, but the lease may be held for longer than the break - * period. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The remaining time in the broken lease in seconds. + * @param breakPeriodInSeconds An optional {@code Integer} representing the proposed duration of seconds that the + * lease should continue before it is broken, between 0 and 60 seconds. This break period is only used if it is + * shorter than the time remaining on the lease. If longer, the time remaining on the lease is used. A new lease + * will not be available before the break period has expired, but the lease may be held for longer than the break + * period. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The remaining time in the broken lease in seconds. */ public Response breakLease(Integer breakPeriodInSeconds, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { + ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { Mono> response = blobAsyncClient .breakLease(breakPeriodInSeconds, modifiedAccessConditions); @@ -743,36 +642,28 @@ public Response breakLease(Integer breakPeriodInSeconds, /** * ChangeLease changes the blob's lease ID. * - * @param leaseId - * The leaseId of the active lease on the blob. - * @param proposedID - * A {@code String} in any valid GUID format. - * - * @return - * The new lease ID. + * @param leaseId The leaseId of the active lease on the blob. + * @param proposedID A {@code String} in any valid GUID format. + * @return The new lease ID. */ public Response changeLease(String leaseId, String proposedID) { return this.changeLease(leaseId, proposedID, null, null); } /** - * ChangeLease changes the blob's lease ID. For more information, see the Azure Docs. - * - * @param leaseId - * The leaseId of the active lease on the blob. - * @param proposedID - * A {@code String} in any valid GUID format. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * ChangeLease changes the blob's lease ID. For more information, see the Azure + * Docs. * + * @param leaseId The leaseId of the active lease on the blob. + * @param proposedID A {@code String} in any valid GUID format. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return The new lease ID. */ public Response changeLease(String leaseId, String proposedID, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { + ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { Mono> response = blobAsyncClient .changeLease(leaseId, proposedID, modifiedAccessConditions); @@ -780,7 +671,8 @@ public Response changeLease(String leaseId, String proposedID, } /** - * Returns the sku name and account kind for the account. For more information, please see the Azure Docs. + * Returns the sku name and account kind for the account. For more information, please see the Azure Docs. * * @return The sku name and account kind. */ @@ -789,11 +681,10 @@ public Response getAccountInfo() { } /** - * Returns the sku name and account kind for the account. For more information, please see the Azure Docs. - * - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * Returns the sku name and account kind for the account. For more information, please see the Azure Docs. * + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return The sku name and account kind. */ public Response getAccountInfo(Duration timeout) { @@ -806,49 +697,33 @@ public Response getAccountInfo(Duration timeout) { /** * Generates a user delegation SAS token with the specified parameters * - * @param userDelegationKey - * The {@code UserDelegationKey} user delegation key for the SAS - * @param accountName - * The {@code String} account name for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * - * @return - * A string that represents the SAS token + * @param userDelegationKey The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName The {@code String} account name for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @return A string that represents the SAS token */ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, - BlobSASPermission permissions, OffsetDateTime expiryTime) { + BlobSASPermission permissions, OffsetDateTime expiryTime) { return this.blobAsyncClient.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime); } /** * Generates a user delegation SAS token with the specified parameters * - * @param userDelegationKey - * The {@code UserDelegationKey} user delegation key for the SAS - * @param accountName - * The {@code String} account name for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * - * @return - * A string that represents the SAS token + * @param userDelegationKey The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName The {@code String} account name for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @return A string that represents the SAS token */ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, - BlobSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, - SASProtocol sasProtocol, IPRange ipRange) { + BlobSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange) { return this.blobAsyncClient.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, startTime, version, sasProtocol, ipRange); } @@ -856,40 +731,25 @@ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, Str /** * Generates a user delegation SAS token with the specified parameters * - * @param userDelegationKey - * The {@code UserDelegationKey} user delegation key for the SAS - * @param accountName - * The {@code String} account name for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * @param cacheControl - * An optional {@code String} cache-control header for the SAS. - * @param contentDisposition - * An optional {@code String} content-disposition header for the SAS. - * @param contentEncoding - * An optional {@code String} content-encoding header for the SAS. - * @param contentLanguage - * An optional {@code String} content-language header for the SAS. - * @param contentType - * An optional {@code String} content-type header for the SAS. - * - * @return - * A string that represents the SAS token + * @param userDelegationKey The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName The {@code String} account name for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @param cacheControl An optional {@code String} cache-control header for the SAS. + * @param contentDisposition An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage An optional {@code String} content-language header for the SAS. + * @param contentType An optional {@code String} content-type header for the SAS. + * @return A string that represents the SAS token */ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, - BlobSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, - SASProtocol sasProtocol, IPRange ipRange, String cacheControl, String contentDisposition, - String contentEncoding, String contentLanguage, String contentType) { + BlobSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange, String cacheControl, String contentDisposition, + String contentEncoding, String contentLanguage, String contentType) { return this.blobAsyncClient.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, startTime, version, sasProtocol, ipRange, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); @@ -898,13 +758,9 @@ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, Str /** * Generates a SAS token with the specified parameters * - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * - * @return - * A string that represents the SAS token + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @return A string that represents the SAS token */ public String generateSAS(OffsetDateTime expiryTime, BlobSASPermission permissions) { return this.blobAsyncClient.generateSAS(permissions, expiryTime); @@ -913,11 +769,8 @@ public String generateSAS(OffsetDateTime expiryTime, BlobSASPermission permissio /** * Generates a SAS token with the specified parameters * - * @param identifier - * The {@code String} name of the access policy on the container this SAS references if any - * - * @return - * A string that represents the SAS token + * @param identifier The {@code String} name of the access policy on the container this SAS references if any + * @return A string that represents the SAS token */ public String generateSAS(String identifier) { return this.blobAsyncClient.generateSAS(identifier); @@ -926,26 +779,17 @@ public String generateSAS(String identifier) { /** * Generates a SAS token with the specified parameters * - * @param identifier - * The {@code String} name of the access policy on the container this SAS references if any - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * - * @return - * A string that represents the SAS token + * @param identifier The {@code String} name of the access policy on the container this SAS references if any + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @return A string that represents the SAS token */ public String generateSAS(String identifier, BlobSASPermission permissions, OffsetDateTime expiryTime, - OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange) { + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange) { return this.blobAsyncClient.generateSAS(identifier, permissions, expiryTime, startTime, version, sasProtocol, ipRange); } @@ -953,37 +797,23 @@ public String generateSAS(String identifier, BlobSASPermission permissions, Offs /** * Generates a SAS token with the specified parameters * - * @param identifier - * The {@code String} name of the access policy on the container this SAS references if any - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * @param cacheControl - * An optional {@code String} cache-control header for the SAS. - * @param contentDisposition - * An optional {@code String} content-disposition header for the SAS. - * @param contentEncoding - * An optional {@code String} content-encoding header for the SAS. - * @param contentLanguage - * An optional {@code String} content-language header for the SAS. - * @param contentType - * An optional {@code String} content-type header for the SAS. - * - * @return - * A string that represents the SAS token + * @param identifier The {@code String} name of the access policy on the container this SAS references if any + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @param cacheControl An optional {@code String} cache-control header for the SAS. + * @param contentDisposition An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage An optional {@code String} content-language header for the SAS. + * @param contentType An optional {@code String} content-type header for the SAS. + * @return A string that represents the SAS token */ public String generateSAS(String identifier, BlobSASPermission permissions, OffsetDateTime expiryTime, - OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange, String cacheControl, - String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange, String cacheControl, + String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { return this.blobAsyncClient.generateSAS(identifier, permissions, expiryTime, startTime, version, sasProtocol, ipRange, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); } @@ -991,8 +821,7 @@ public String generateSAS(String identifier, BlobSASPermission permissions, Offs /** * Gets the snapshotId for a blob resource * - * @return - * A string that represents the snapshotId of the snapshot blob + * @return A string that represents the snapshotId of the snapshot blob */ public String getSnapshotId() { return this.blobAsyncClient.getSnapshotId(); @@ -1001,8 +830,7 @@ public String getSnapshotId() { /** * Determines if a blob is a snapshot * - * @return - * A boolean that indicates if a blob is a snapshot + * @return A boolean that indicates if a blob is a snapshot */ public boolean isSnapshot() { return this.blobAsyncClient.isSnapshot(); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobInputStream.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobInputStream.java index 2e093f50738f2..1a990e179688b 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobInputStream.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobInputStream.java @@ -189,7 +189,7 @@ public synchronized void close() throws IOException { */ private synchronized void dispatchRead(final int readLength) throws IOException { try { - this.currentBuffer = this.blobClient.blobAsyncRawClient.download(new BlobRange(this.currentAbsoluteReadPosition, (long) readLength), this.accessCondition, false) + this.currentBuffer = this.blobClient.download(new BlobRange(this.currentAbsoluteReadPosition, (long) readLength), this.accessCondition, false) .flatMap(res -> ByteBufFlux.fromInbound(res.body(null)).aggregate().asByteBuffer()) .block(); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobOutputStream.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobOutputStream.java index 176fe06c321d1..c733a580f2500 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobOutputStream.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobOutputStream.java @@ -295,7 +295,7 @@ private Mono writePages(Flux pageData, long offset, long writeLen PageBlobAccessConditions pageBlobAccessConditions = accessCondition == null ? null : new PageBlobAccessConditions().leaseAccessConditions(accessCondition.leaseAccessConditions()).modifiedAccessConditions(accessCondition.modifiedAccessConditions()); - return blobRef.pageBlobAsyncRawClient.uploadPages(new PageRange().start(offset).end(offset + writeLength - 1), pageData, pageBlobAccessConditions) + return blobRef.uploadPages(new PageRange().start(offset).end(offset + writeLength - 1), pageData, pageBlobAccessConditions) .then() .onErrorResume(t -> t instanceof StorageException, e -> { this.lastError = new IOException(e); @@ -312,7 +312,7 @@ private Mono appendBlock(Flux blockData, long offset, long writeL this.appendPositionAccessConditions.appendPosition(offset); AppendBlobAccessConditions appendBlobAccessConditions = accessCondition == null ? null : new AppendBlobAccessConditions().leaseAccessConditions(accessCondition.leaseAccessConditions()).modifiedAccessConditions(accessCondition.modifiedAccessConditions()); - return blobRef.appendBlobAsyncRawClient.appendBlock(blockData, writeLength, appendBlobAccessConditions) + return blobRef.appendBlock(blockData, writeLength, appendBlobAccessConditions) .then() .onErrorResume(t -> t instanceof IOException || t instanceof StorageException, e -> { this.lastError = new IOException(e); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobURLParts.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobURLParts.java index 473d998c446b2..1200e960f7501 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobURLParts.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobURLParts.java @@ -76,14 +76,14 @@ public BlobURLParts host(String host) { } /** - * The container name or {@code null} if a {@link StorageAsyncRawClient} was parsed. + * The container name or {@code null} if a {@link StorageAsyncClient} was parsed. */ public String containerName() { return containerName; } /** - * The container name or {@code null} if a {@link StorageAsyncRawClient} was parsed. + * The container name or {@code null} if a {@link StorageAsyncClient} was parsed. */ public BlobURLParts containerName(String containerName) { this.containerName = containerName; @@ -91,14 +91,14 @@ public BlobURLParts containerName(String containerName) { } /** - * The blob name or {@code null} if a {@link StorageAsyncRawClient} or {@link ContainerAsyncClient} was parsed. + * The blob name or {@code null} if a {@link StorageAsyncClient} or {@link ContainerAsyncClient} was parsed. */ public String blobName() { return blobName; } /** - * The blob name or {@code null} if a {@link StorageAsyncRawClient} or {@link ContainerAsyncClient} was parsed. + * The blob name or {@code null} if a {@link StorageAsyncClient} or {@link ContainerAsyncClient} was parsed. */ public BlobURLParts blobName(String blobName) { this.blobName = blobName; diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncClient.java index ada7a125d4ef1..e7d8aa9b0c03d 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncClient.java @@ -8,6 +8,7 @@ import com.azure.core.http.rest.SimpleResponse; import com.azure.core.http.rest.VoidResponse; import com.azure.core.implementation.util.FluxUtil; +import com.azure.core.util.Context; import com.azure.storage.blob.implementation.AzureBlobStorageBuilder; import com.azure.storage.blob.models.BlobAccessConditions; import com.azure.storage.blob.models.BlobHTTPHeaders; @@ -15,11 +16,11 @@ import com.azure.storage.blob.models.BlockBlobItem; import com.azure.storage.blob.models.BlockItem; import com.azure.storage.blob.models.BlockListType; +import com.azure.storage.blob.models.BlockLookupList; import com.azure.storage.blob.models.LeaseAccessConditions; import com.azure.storage.blob.models.Metadata; import com.azure.storage.blob.models.SourceModifiedAccessConditions; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -27,7 +28,6 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.net.URL; -import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; @@ -39,6 +39,8 @@ import java.util.TreeMap; import java.util.UUID; +import static com.azure.storage.blob.Utility.postProcessResponse; + /** * Client to a block blob. It may only be instantiated through a {@link BlockBlobClientBuilder}, via * the method {@link BlobAsyncClient#asBlockBlobAsyncClient()}, or via the method @@ -66,8 +68,6 @@ public final class BlockBlobAsyncClient extends BlobAsyncClient { static final int BLOB_DEFAULT_UPLOAD_BLOCK_SIZE = 4 * Constants.MB; static final int BLOB_MAX_UPLOAD_BLOCK_SIZE = 100 * Constants.MB; - final BlockBlobAsyncRawClient blockBlobAsyncRawClient; - /** * Indicates the maximum number of bytes that can be sent in a call to upload. */ @@ -89,7 +89,6 @@ public final class BlockBlobAsyncClient extends BlobAsyncClient { */ BlockBlobAsyncClient(AzureBlobStorageBuilder azureBlobStorageBuilder, String snapshot) { super(azureBlobStorageBuilder, snapshot); - this.blockBlobAsyncRawClient = new BlockBlobAsyncRawClient(azureBlobStorageBuilder.build(), snapshot); } /** @@ -114,7 +113,7 @@ public final class BlockBlobAsyncClient extends BlobAsyncClient { * @return * A reactive response containing the information of the uploaded block blob. */ - public Mono> upload(Flux data, long length) { + public Mono> upload(Flux data, long length) { return this.upload(data, length, null, null, null); } @@ -146,10 +145,15 @@ public Mono> upload(Flux data, long length) * @return * A reactive response containing the information of the uploaded block blob. */ - public Mono> upload(Flux data, long length, BlobHTTPHeaders headers, + public Mono> upload(Flux data, long length, BlobHTTPHeaders headers, Metadata metadata, BlobAccessConditions accessConditions) { - return blockBlobAsyncRawClient - .upload(data.map(Unpooled::wrappedBuffer), length, headers, metadata, accessConditions) + metadata = metadata == null ? new Metadata() : metadata; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blockBlobs().uploadWithRestResponseAsync(null, + null, data, length, null, metadata, null, null, + null, null, headers, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new BlockBlobItem(rb.deserializedHeaders()))); } @@ -291,8 +295,9 @@ public Mono stageBlock(String base64BlockID, Flux data, */ public Mono stageBlock(String base64BlockID, Flux data, long length, LeaseAccessConditions leaseAccessConditions) { - return blockBlobAsyncRawClient - .stageBlock(base64BlockID, data, length, leaseAccessConditions) + return postProcessResponse(this.azureBlobStorage.blockBlobs().stageBlockWithRestResponseAsync(null, + null, base64BlockID, length, data, null, null, null, + null, null, null, leaseAccessConditions, Context.NONE)) .map(VoidResponse::new); } @@ -349,8 +354,13 @@ public Mono stageBlockFromURL(String base64BlockID, URL sourceURL, public Mono stageBlockFromURL(String base64BlockID, URL sourceURL, BlobRange sourceRange, byte[] sourceContentMD5, LeaseAccessConditions leaseAccessConditions, SourceModifiedAccessConditions sourceModifiedAccessConditions) { - return blockBlobAsyncRawClient - .stageBlockFromURL(base64BlockID, sourceURL, sourceRange, sourceContentMD5, leaseAccessConditions, sourceModifiedAccessConditions) + sourceRange = sourceRange == null ? new BlobRange(0) : sourceRange; + + return postProcessResponse( + this.azureBlobStorage.blockBlobs().stageBlockFromURLWithRestResponseAsync(null, null, + base64BlockID, 0, sourceURL, sourceRange.toHeaderValue(), sourceContentMD5, null, + null, null, null, null, + leaseAccessConditions, sourceModifiedAccessConditions, Context.NONE)) .map(VoidResponse::new); } @@ -386,8 +396,9 @@ public Flux listBlocks(BlockListType listType) { */ public Flux listBlocks(BlockListType listType, LeaseAccessConditions leaseAccessConditions) { - return blockBlobAsyncRawClient - .listBlocks(listType, leaseAccessConditions) + return postProcessResponse(this.azureBlobStorage.blockBlobs().getBlockListWithRestResponseAsync( + null, null, listType, snapshot, null, null, null, + leaseAccessConditions, Context.NONE)) .map(ResponseBase::value) .flatMapMany(bl -> { Flux committed = Flux.fromIterable(bl.committedBlocks()) @@ -440,8 +451,13 @@ public Mono> commitBlockList(List base64BlockIDs */ public Mono> commitBlockList(List base64BlockIDs, BlobHTTPHeaders headers, Metadata metadata, BlobAccessConditions accessConditions) { - return blockBlobAsyncRawClient - .commitBlockList(base64BlockIDs, headers, metadata, accessConditions) + metadata = metadata == null ? new Metadata() : metadata; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.blockBlobs().commitBlockListWithRestResponseAsync( + null, null, new BlockLookupList().latest(base64BlockIDs), null, metadata, + null, null, null, null, headers, + accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new BlockBlobItem(rb.deserializedHeaders()))); } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncRawClient.java deleted file mode 100644 index 03b7e650167b6..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobAsyncRawClient.java +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.util.Context; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.BlobAccessConditions; -import com.azure.storage.blob.models.BlobHTTPHeaders; -import com.azure.storage.blob.models.BlobRange; -import com.azure.storage.blob.models.BlockBlobsCommitBlockListResponse; -import com.azure.storage.blob.models.BlockBlobsGetBlockListResponse; -import com.azure.storage.blob.models.BlockBlobsStageBlockFromURLResponse; -import com.azure.storage.blob.models.BlockBlobsStageBlockResponse; -import com.azure.storage.blob.models.BlockBlobsUploadResponse; -import com.azure.storage.blob.models.BlockListType; -import com.azure.storage.blob.models.BlockLookupList; -import com.azure.storage.blob.models.LeaseAccessConditions; -import com.azure.storage.blob.models.Metadata; -import com.azure.storage.blob.models.SourceModifiedAccessConditions; -import io.netty.buffer.ByteBuf; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.net.URL; -import java.util.List; - -import static com.azure.storage.blob.Utility.postProcessResponse; - -/** - * Represents a URL to a block blob. It may be obtained by direct construction or via the create method on a - * {@link ContainerAsyncClient} object. This class does not hold any state about a particular blob but is instead a convenient - * way of sending off appropriate requests to the resource on the service. Please refer to the - * Azure Docs - * for more information on block blobs. - */ -final class BlockBlobAsyncRawClient extends BlobAsyncRawClient { - - /** - * Indicates the maximum number of bytes that can be sent in a call to upload. - */ - public static final int MAX_UPLOAD_BLOB_BYTES = 256 * Constants.MB; - - /** - * Indicates the maximum number of bytes that can be sent in a call to stageBlock. - */ - public static final int MAX_STAGE_BLOCK_BYTES = 100 * Constants.MB; - - /** - * Indicates the maximum number of blocks allowed in a block blob. - */ - public static final int MAX_BLOCKS = 50000; - - /** - * Creates a {@code BlockBlobAsyncRawClient} object pointing to the account specified by the URL and using the provided - */ - BlockBlobAsyncRawClient(AzureBlobStorageImpl azureBlobStorage, String snapshot) { - super(azureBlobStorage, snapshot); - } - - - /** - * Creates a new block blob, or updates the content of an existing block blob. - * Updating an existing block blob overwrites any existing metadata on the blob. Partial updates are not - * supported with PutBlob; the content of the existing blob is overwritten with the new content. To - * perform a partial update of a block blob's, use PutBlock and PutBlockList. - * For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - *

- * - * @param data - * The data to write to the blob. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param length - * The exact length of the data. It is important that this value match precisely the length of the data - * emitted by the {@code Flux}. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=upload_download "Sample code for BlockBlobAsyncRawClient.upload")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono upload(Flux data, long length) { - return this.upload(data, length, null, null, null); - } - - /** - * Creates a new block blob, or updates the content of an existing block blob. - * Updating an existing block blob overwrites any existing metadata on the blob. Partial updates are not - * supported with PutBlob; the content of the existing blob is overwritten with the new content. To - * perform a partial update of a block blob's, use PutBlock and PutBlockList. - * For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - *

- * - * @param data - * The data to write to the blob. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param length - * The exact length of the data. It is important that this value match precisely the length of the data - * emitted by the {@code Flux}. - * @param headers - * {@link BlobHTTPHeaders} - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=upload_download "Sample code for BlockBlobAsyncRawClient.upload")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono upload(Flux data, long length, BlobHTTPHeaders headers, - Metadata metadata, BlobAccessConditions accessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blockBlobs().uploadWithRestResponseAsync(null, - null, data, length, null, metadata, null, null, - null, null, headers, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Uploads the specified block to the block blob's "staging area" to be later committed by a call to - * commitBlockList. For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - * - * @param base64BlockID - * A Base64 encoded {@code String} that specifies the ID for this block. Note that all block ids for a given - * blob must be the same length. - * @param data - * The data to write to the block. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param length - * The exact length of the data. It is important that this value match precisely the length of the data - * emitted by the {@code Flux}. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blocks "Sample code for BlockBlobAsyncRawClient.stageBlock")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono stageBlock(String base64BlockID, Flux data, - long length) { - return this.stageBlock(base64BlockID, data, length, null); - } - - /** - * Uploads the specified block to the block blob's "staging area" to be later committed by a call to - * commitBlockList. For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - * - * @param base64BlockID - * A Base64 encoded {@code String} that specifies the ID for this block. Note that all block ids for a given - * blob must be the same length. - * @param data - * The data to write to the block. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param length - * The exact length of the data. It is important that this value match precisely the length of the data - * emitted by the {@code Flux}. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blocks "Sample code for BlockBlobAsyncRawClient.stageBlock")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono stageBlock(String base64BlockID, Flux data, long length, - LeaseAccessConditions leaseAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blockBlobs().stageBlockWithRestResponseAsync(null, - null, base64BlockID, length, data, null, null, null, - null, null, null, leaseAccessConditions, Context.NONE)); - } - - /** - * Creates a new block to be committed as part of a blob where the contents are read from a URL. For more - * information, see the Azure Docs. - * - * @param base64BlockID - * A Base64 encoded {@code String} that specifies the ID for this block. Note that all block ids for a given - * blob must be the same length. - * @param sourceURL - * The url to the blob that will be the source of the copy. A source blob in the same storage account can be - * authenticated via Shared Key. However, if the source is a blob in another account, the source blob must - * either be public or must be authenticated via a shared access signature. If the source blob is public, no - * authentication is required to perform the operation. - * @param sourceRange - * {@link BlobRange} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=block_from_url "Sample code for BlockBlobAsyncRawClient.stageBlockFromURL")] - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono stageBlockFromURL(String base64BlockID, URL sourceURL, - BlobRange sourceRange) { - return this.stageBlockFromURL(base64BlockID, sourceURL, sourceRange, null, - null, null); - } - - /** - * Creates a new block to be committed as part of a blob where the contents are read from a URL. For more - * information, see the Azure Docs. - * - * @param base64BlockID - * A Base64 encoded {@code String} that specifies the ID for this block. Note that all block ids for a given - * blob must be the same length. - * @param sourceURL - * The url to the blob that will be the source of the copy. A source blob in the same storage account can - * be authenticated via Shared Key. However, if the source is a blob in another account, the source blob - * must either be public or must be authenticated via a shared access signature. If the source blob is - * public, no authentication is required to perform the operation. - * @param sourceRange - * {@link BlobRange} - * @param sourceContentMD5 - * An MD5 hash of the block content from the source blob. If specified, the service will calculate the MD5 - * of the received data and fail the request if it does not match the provided MD5. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * @param sourceModifiedAccessConditions - * {@link SourceModifiedAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=block_from_url "Sample code for BlockBlobAsyncRawClient.stageBlockFromURL")] - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono stageBlockFromURL(String base64BlockID, URL sourceURL, - BlobRange sourceRange, byte[] sourceContentMD5, LeaseAccessConditions leaseAccessConditions, - SourceModifiedAccessConditions sourceModifiedAccessConditions) { - sourceRange = sourceRange == null ? new BlobRange(0) : sourceRange; - - return postProcessResponse( - this.azureBlobStorage.blockBlobs().stageBlockFromURLWithRestResponseAsync(null, null, - base64BlockID, 0, sourceURL, sourceRange.toHeaderValue(), sourceContentMD5, null, - null, null, null, null, - leaseAccessConditions, sourceModifiedAccessConditions, Context.NONE)); - } - - /** - * Returns the list of blocks that have been uploaded as part of a block blob using the specified block list filter. - * For more information, see the - * Azure Docs. - * - * @param listType - * Specifies which type of blocks to return. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blocks "Sample code for BlockBlobAsyncRawClient.listBlocks")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono listBlocks(BlockListType listType) { - return this.listBlocks(listType, null); - } - - /** - * Returns the list of blocks that have been uploaded as part of a block blob using the specified block list filter. - * For more information, see the - * Azure Docs. - * - * @param listType - * Specifies which type of blocks to return. - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blocks "Sample code for BlockBlobAsyncRawClient.listBlocks")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono listBlocks(BlockListType listType, - LeaseAccessConditions leaseAccessConditions) { - return postProcessResponse(this.azureBlobStorage.blockBlobs().getBlockListWithRestResponseAsync( - null, null, listType, snapshot, null, null, null, - leaseAccessConditions, Context.NONE)); - } - - /** - * Writes a blob by specifying the list of block IDs that are to make up the blob. - * In order to be written as part of a blob, a block must have been successfully written - * to the server in a prior stageBlock operation. You can call commitBlockList to update a blob - * by uploading only those blocks that have changed, then committing the new and existing - * blocks together. Any blocks not specified in the block list and permanently deleted. - * For more information, see the - * Azure Docs. - *

- * - * @param base64BlockIDs - * A list of base64 encode {@code String}s that specifies the block IDs to be committed. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blocks "Sample code for BlockBlobAsyncRawClient.commitBlockList")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono commitBlockList(List base64BlockIDs) { - return this.commitBlockList(base64BlockIDs, null, null, null); - } - - /** - * Writes a blob by specifying the list of block IDs that are to make up the blob. - * In order to be written as part of a blob, a block must have been successfully written - * to the server in a prior stageBlock operation. You can call commitBlockList to update a blob - * by uploading only those blocks that have changed, then committing the new and existing - * blocks together. Any blocks not specified in the block list and permanently deleted. - * For more information, see the - * Azure Docs. - *

- * - * @param base64BlockIDs - * A list of base64 encode {@code String}s that specifies the block IDs to be committed. - * @param headers - * {@link BlobHTTPHeaders} - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=blocks "Sample code for BlockBlobAsyncRawClient.commitBlockList")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono commitBlockList(List base64BlockIDs, - BlobHTTPHeaders headers, Metadata metadata, BlobAccessConditions accessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.blockBlobs().commitBlockListWithRestResponseAsync( - null, null, new BlockLookupList().latest(base64BlockIDs), null, metadata, - null, null, null, null, headers, - accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobClient.java index 9dced1f87022b..ec76f684c1bb4 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobClient.java @@ -4,7 +4,6 @@ package com.azure.storage.blob; import com.azure.core.http.rest.Response; -import com.azure.core.http.rest.SimpleResponse; import com.azure.core.http.rest.VoidResponse; import com.azure.storage.blob.models.BlobAccessConditions; import com.azure.storage.blob.models.BlobHTTPHeaders; @@ -44,22 +43,22 @@ * for more information. */ public final class BlockBlobClient extends BlobClient { + private final BlockBlobAsyncClient blockBlobAsyncClient; - private BlockBlobAsyncClient blockBlobAsyncClient; /** * Indicates the maximum number of bytes that can be sent in a call to upload. */ - public static final int MAX_UPLOAD_BLOB_BYTES = 256 * Constants.MB; + public static final int MAX_UPLOAD_BLOB_BYTES = BlockBlobAsyncClient.MAX_UPLOAD_BLOB_BYTES; /** * Indicates the maximum number of bytes that can be sent in a call to stageBlock. */ - public static final int MAX_STAGE_BLOCK_BYTES = 100 * Constants.MB; + public static final int MAX_STAGE_BLOCK_BYTES = BlockBlobAsyncClient.MAX_STAGE_BLOCK_BYTES; /** * Indicates the maximum number of blocks allowed in a block blob. */ - public static final int MAX_BLOCKS = 50000; + public static final int MAX_BLOCKS = BlockBlobAsyncClient.MAX_BLOCKS; /** * Package-private constructor for use by {@link BlockBlobClientBuilder}. @@ -161,9 +160,8 @@ public Response upload(InputStream data, long length, BlobHTTPHea return ByteBufAllocator.DEFAULT.buffer((int) count).writeBytes(cache); })); - Mono> upload = blockBlobAsyncClient.blockBlobAsyncRawClient - .upload(fbb.subscribeOn(Schedulers.elastic()), length, headers, metadata, accessConditions) - .map(rb -> new SimpleResponse<>(rb, new BlockBlobItem(rb.deserializedHeaders()))); + Mono> upload = blockBlobAsyncClient + .upload(fbb.subscribeOn(Schedulers.elastic()), length, headers, metadata, accessConditions); try { return Utility.blockWithOptionalTimeout(upload, timeout); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncClient.java index f979f084ac151..6d9e64f16a924 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncClient.java @@ -9,6 +9,7 @@ import com.azure.core.http.rest.VoidResponse; import com.azure.core.util.Context; import com.azure.storage.blob.implementation.AzureBlobStorageBuilder; +import com.azure.storage.blob.implementation.AzureBlobStorageImpl; import com.azure.storage.blob.models.BlobFlatListSegment; import com.azure.storage.blob.models.BlobHierarchyListSegment; import com.azure.storage.blob.models.BlobItem; @@ -33,57 +34,59 @@ import java.net.URL; import java.time.Duration; import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; import java.util.List; +import static com.azure.storage.blob.Utility.postProcessResponse; + /** - * Client to a container. It may only be instantiated through a {@link ContainerClientBuilder} or via the method - * {@link StorageAsyncClient#getContainerAsyncClient(String)}. This class does not hold any - * state about a particular blob but is instead a convenient way of sending off appropriate requests to - * the resource on the service. It may also be used to construct URLs to blobs. + * Client to a container. It may only be instantiated through a {@link ContainerClientBuilder} or via the method {@link + * StorageAsyncClient#getContainerAsyncClient(String)}. This class does not hold any state about a particular blob but + * is instead a convenient way of sending off appropriate requests to the resource on the service. It may also be used + * to construct URLs to blobs. * *

* This client contains operations on a container. Operations on a blob are available on {@link BlobAsyncClient} through * {@link #getBlobAsyncClient(String)}, and operations on the service are available on {@link StorageAsyncClient}. * *

- * Please refer to the Azure Docs - * for more information on containers. + * Please refer to the Azure + * Docs for more information on containers. * *

- * Note this client is an async client that returns reactive responses from Spring Reactor Core - * project (https://projectreactor.io/). Calling the methods in this client will NOT - * start the actual network operation, until {@code .subscribe()} is called on the reactive response. - * You can simply convert one of these responses to a {@link java.util.concurrent.CompletableFuture} - * object through {@link Mono#toFuture()}. + * Note this client is an async client that returns reactive responses from Spring Reactor Core project + * (https://projectreactor.io/). Calling the methods in this client will NOT start the actual network + * operation, until {@code .subscribe()} is called on the reactive response. You can simply convert one of these + * responses to a {@link java.util.concurrent.CompletableFuture} object through {@link Mono#toFuture()}. */ public final class ContainerAsyncClient { - ContainerAsyncRawClient containerAsyncRawClient; - public static final String ROOT_CONTAINER_NAME = "$root"; public static final String STATIC_WEBSITE_CONTAINER_NAME = "$web"; public static final String LOG_CONTAINER_NAME = "$logs"; + private final AzureBlobStorageImpl azureBlobStorage; + /** * Package-private constructor for use by {@link ContainerClientBuilder}. + * * @param azureBlobStorageBuilder the API client builder for blob storage API */ ContainerAsyncClient(AzureBlobStorageBuilder azureBlobStorageBuilder) { - this.containerAsyncRawClient = new ContainerAsyncRawClient(azureBlobStorageBuilder.build()); + this.azureBlobStorage = azureBlobStorageBuilder.build(); } /** * Creates a new {@link BlockBlobAsyncClient} object by concatenating the blobName to the end of - * ContainerAsyncClient's URL. The new BlockBlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the BlockBlobAsyncClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's NewBlockBlobAsyncClient instead of calling this object's - * NewBlockBlobAsyncClient method. - * - * @param blobName - * A {@code String} representing the name of the blob. - * - * @return A new {@link BlockBlobAsyncClient} object which references the blob with the specified name in this container. + * ContainerAsyncClient's URL. The new BlockBlobAsyncClient uses the same request policy pipeline as the + * ContainerAsyncClient. To change the pipeline, create the BlockBlobAsyncClient and then call its WithPipeline + * method passing in the desired pipeline object. Or, call this package's NewBlockBlobAsyncClient instead of calling + * this object's NewBlockBlobAsyncClient method. + * + * @param blobName A {@code String} representing the name of the blob. + * @return A new {@link BlockBlobAsyncClient} object which references the blob with the specified name in this + * container. */ public BlockBlobAsyncClient getBlockBlobAsyncClient(String blobName) { return getBlockBlobAsyncClient(blobName, null); @@ -91,106 +94,95 @@ public BlockBlobAsyncClient getBlockBlobAsyncClient(String blobName) { /** * Creates a new {@link BlockBlobAsyncClient} object by concatenating the blobName to the end of - * ContainerAsyncClient's URL. The new BlockBlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the BlockBlobAsyncClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's NewBlockBlobAsyncClient instead of calling this object's - * NewBlockBlobAsyncClient method. - * - * @param blobName - * A {@code String} representing the name of the blob. - * @param snapshot - * the snapshot identifier for the blob. - * - * @return A new {@link BlockBlobAsyncClient} object which references the blob with the specified name in this container. + * ContainerAsyncClient's URL. The new BlockBlobAsyncClient uses the same request policy pipeline as the + * ContainerAsyncClient. To change the pipeline, create the BlockBlobAsyncClient and then call its WithPipeline + * method passing in the desired pipeline object. Or, call this package's NewBlockBlobAsyncClient instead of calling + * this object's NewBlockBlobAsyncClient method. + * + * @param blobName A {@code String} representing the name of the blob. + * @param snapshot the snapshot identifier for the blob. + * @return A new {@link BlockBlobAsyncClient} object which references the blob with the specified name in this + * container. */ public BlockBlobAsyncClient getBlockBlobAsyncClient(String blobName, String snapshot) { return new BlockBlobAsyncClient(new AzureBlobStorageBuilder() .url(Utility.appendToURLPath(getContainerUrl(), blobName).toString()) - .pipeline(containerAsyncRawClient.azureBlobStorage.httpPipeline()), snapshot); + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** - * Creates creates a new PageBlobAsyncClient object by concatenating blobName to the end of - * ContainerAsyncClient's URL. The new PageBlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the PageBlobAsyncClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's NewPageBlobAsyncClient instead of calling this object's - * NewPageBlobAsyncClient method. - * - * @param blobName - * A {@code String} representing the name of the blob. - * - * @return A new {@link PageBlobAsyncClient} object which references the blob with the specified name in this container. + * Creates creates a new PageBlobAsyncClient object by concatenating blobName to the end of ContainerAsyncClient's + * URL. The new PageBlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. To change the + * pipeline, create the PageBlobAsyncClient and then call its WithPipeline method passing in the desired pipeline + * object. Or, call this package's NewPageBlobAsyncClient instead of calling this object's NewPageBlobAsyncClient + * method. + * + * @param blobName A {@code String} representing the name of the blob. + * @return A new {@link PageBlobAsyncClient} object which references the blob with the specified name in this + * container. */ public PageBlobAsyncClient getPageBlobAsyncClient(String blobName) { return getPageBlobAsyncClient(blobName, null); } /** - * Creates creates a new PageBlobAsyncClient object by concatenating blobName to the end of - * ContainerAsyncClient's URL. The new PageBlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the PageBlobAsyncClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's NewPageBlobAsyncClient instead of calling this object's - * NewPageBlobAsyncClient method. - * - * @param blobName - * A {@code String} representing the name of the blob. - * @param snapshot - * the snapshot identifier for the blob. - * - * @return A new {@link PageBlobAsyncClient} object which references the blob with the specified name in this container. + * Creates creates a new PageBlobAsyncClient object by concatenating blobName to the end of ContainerAsyncClient's + * URL. The new PageBlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. To change the + * pipeline, create the PageBlobAsyncClient and then call its WithPipeline method passing in the desired pipeline + * object. Or, call this package's NewPageBlobAsyncClient instead of calling this object's NewPageBlobAsyncClient + * method. + * + * @param blobName A {@code String} representing the name of the blob. + * @param snapshot the snapshot identifier for the blob. + * @return A new {@link PageBlobAsyncClient} object which references the blob with the specified name in this + * container. */ public PageBlobAsyncClient getPageBlobAsyncClient(String blobName, String snapshot) { return new PageBlobAsyncClient(new AzureBlobStorageBuilder() .url(Utility.appendToURLPath(getContainerUrl(), blobName).toString()) - .pipeline(containerAsyncRawClient.azureBlobStorage.httpPipeline()), snapshot); + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** - * Creates creates a new AppendBlobAsyncClient object by concatenating blobName to the end of - * ContainerAsyncClient's URL. The new AppendBlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the AppendBlobAsyncClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's NewAppendBlobAsyncClient instead of calling this object's + * Creates creates a new AppendBlobAsyncClient object by concatenating blobName to the end of ContainerAsyncClient's + * URL. The new AppendBlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. To change + * the pipeline, create the AppendBlobAsyncClient and then call its WithPipeline method passing in the desired + * pipeline object. Or, call this package's NewAppendBlobAsyncClient instead of calling this object's * NewAppendBlobAsyncClient method. * - * @param blobName - * A {@code String} representing the name of the blob. - * - * @return A new {@link AppendBlobAsyncClient} object which references the blob with the specified name in this container. + * @param blobName A {@code String} representing the name of the blob. + * @return A new {@link AppendBlobAsyncClient} object which references the blob with the specified name in this + * container. */ public AppendBlobAsyncClient getAppendBlobAsyncClient(String blobName) { return getAppendBlobAsyncClient(blobName, null); } /** - * Creates creates a new AppendBlobAsyncClient object by concatenating blobName to the end of - * ContainerAsyncClient's URL. The new AppendBlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the AppendBlobAsyncClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's NewAppendBlobAsyncClient instead of calling this object's + * Creates creates a new AppendBlobAsyncClient object by concatenating blobName to the end of ContainerAsyncClient's + * URL. The new AppendBlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. To change + * the pipeline, create the AppendBlobAsyncClient and then call its WithPipeline method passing in the desired + * pipeline object. Or, call this package's NewAppendBlobAsyncClient instead of calling this object's * NewAppendBlobAsyncClient method. * - * @param blobName - * A {@code String} representing the name of the blob. - * @param snapshot - * the snapshot identifier for the blob. - * - * @return A new {@link AppendBlobAsyncClient} object which references the blob with the specified name in this container. + * @param blobName A {@code String} representing the name of the blob. + * @param snapshot the snapshot identifier for the blob. + * @return A new {@link AppendBlobAsyncClient} object which references the blob with the specified name in this + * container. */ public AppendBlobAsyncClient getAppendBlobAsyncClient(String blobName, String snapshot) { return new AppendBlobAsyncClient(new AzureBlobStorageBuilder() .url(Utility.appendToURLPath(getContainerUrl(), blobName).toString()) - .pipeline(containerAsyncRawClient.azureBlobStorage.httpPipeline()), snapshot); + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** - * Creates a new BlobAsyncClient object by concatenating blobName to the end of - * ContainerAsyncClient's URL. The new BlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the BlobAsyncClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's getBlobAsyncClient instead of calling this object's - * getBlobAsyncClient method. - * - * @param blobName - * A {@code String} representing the name of the blob. + * Creates a new BlobAsyncClient object by concatenating blobName to the end of ContainerAsyncClient's URL. The new + * BlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. To change the pipeline, create + * the BlobAsyncClient and then call its WithPipeline method passing in the desired pipeline object. Or, call this + * package's getBlobAsyncClient instead of calling this object's getBlobAsyncClient method. * + * @param blobName A {@code String} representing the name of the blob. * @return A new {@link BlobAsyncClient} object which references the blob with the specified name in this container. */ public BlobAsyncClient getBlobAsyncClient(String blobName) { @@ -198,55 +190,50 @@ public BlobAsyncClient getBlobAsyncClient(String blobName) { } /** - * Creates a new BlobAsyncClient object by concatenating blobName to the end of - * ContainerAsyncClient's URL. The new BlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the BlobAsyncClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's getBlobAsyncClient instead of calling this object's - * getBlobAsyncClient method. - * - * @param blobName - * A {@code String} representing the name of the blob. - * @param snapshot - * the snapshot identifier for the blob. + * Creates a new BlobAsyncClient object by concatenating blobName to the end of ContainerAsyncClient's URL. The new + * BlobAsyncClient uses the same request policy pipeline as the ContainerAsyncClient. To change the pipeline, create + * the BlobAsyncClient and then call its WithPipeline method passing in the desired pipeline object. Or, call this + * package's getBlobAsyncClient instead of calling this object's getBlobAsyncClient method. * + * @param blobName A {@code String} representing the name of the blob. + * @param snapshot the snapshot identifier for the blob. * @return A new {@link BlobAsyncClient} object which references the blob with the specified name in this container. */ public BlobAsyncClient getBlobAsyncClient(String blobName, String snapshot) { return new BlobAsyncClient(new AzureBlobStorageBuilder() .url(Utility.appendToURLPath(getContainerUrl(), blobName).toString()) - .pipeline(containerAsyncRawClient.azureBlobStorage.httpPipeline()), snapshot); + .pipeline(azureBlobStorage.httpPipeline()), snapshot); } /** * Initializes a {@link StorageAsyncClient} object pointing to the storage account this container is in. * - * @return - * A {@link StorageAsyncClient} object pointing to the specified storage account + * @return A {@link StorageAsyncClient} object pointing to the specified storage account */ public StorageAsyncClient getStorageAsyncClient() { return new StorageAsyncClient(new AzureBlobStorageBuilder() .url(Utility.stripLastPathSegment(getContainerUrl()).toString()) - .pipeline(containerAsyncRawClient.azureBlobStorage.httpPipeline())); + .pipeline(azureBlobStorage.httpPipeline())); } /** * Gets the URL of the container represented by this client. + * * @return the URL. * @throws RuntimeException If the container has a malformed URL. */ public URL getContainerUrl() { try { - return new URL(containerAsyncRawClient.azureBlobStorage.url()); + return new URL(azureBlobStorage.url()); } catch (MalformedURLException e) { - throw new RuntimeException(String.format("Invalid URL on %s: %s" + getClass().getSimpleName(), containerAsyncRawClient.azureBlobStorage.url()), e); + throw new RuntimeException(String.format("Invalid URL on %s: %s" + getClass().getSimpleName(), azureBlobStorage.url()), e); } } /** * Gets if the container this client represents exists in the cloud. * - * @return - * true if the container exists, false if it doesn't + * @return true if the container exists, false if it doesn't */ public Mono> exists() { return this.getProperties(null) @@ -262,8 +249,7 @@ public Mono> exists() { * fails. For more information, see the * Azure Docs. * - * @return - * A reactive response signalling completion. + * @return A reactive response signalling completion. */ public Mono create() { return this.create(null, null); @@ -274,47 +260,52 @@ public Mono create() { * fails. For more information, see the * Azure Docs. * - * @param metadata - * {@link Metadata} - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * - * @return - * A reactive response signalling completion. + * @param metadata {@link Metadata} + * @param accessType Specifies how the data in this container is available to the public. See the + * x-ms-blob-public-access header in the Azure Docs for more information. Pass null for no public access. + * @return A reactive response signalling completion. */ public Mono create(Metadata metadata, PublicAccessType accessType) { - return containerAsyncRawClient - .create(metadata, accessType) + metadata = metadata == null ? new Metadata() : metadata; + + return postProcessResponse(this.azureBlobStorage.containers().createWithRestResponseAsync( + null, null, metadata, accessType, null, Context.NONE)) .map(VoidResponse::new); } /** - * Marks the specified container for deletion. The container and any blobs contained within it are later - * deleted during garbage collection. For more information, see the + * Marks the specified container for deletion. The container and any blobs contained within it are later deleted + * during garbage collection. For more information, see the * Azure Docs. * - * @return - * A reactive response signalling completion. + * @return A reactive response signalling completion. */ public Mono delete() { return this.delete(null); } /** - * Marks the specified container for deletion. The container and any blobs contained within it are later - * deleted during garbage collection. For more information, see the + * Marks the specified container for deletion. The container and any blobs contained within it are later deleted + * during garbage collection. For more information, see the * Azure Docs. * - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return - * A reactive response signalling completion. + * @param accessConditions {@link ContainerAccessConditions} + * @return A reactive response signalling completion. + * @throws UnsupportedOperationException If {@link ContainerAccessConditions#modifiedAccessConditions()} has either + * {@link ModifiedAccessConditions#ifMatch()} or {@link ModifiedAccessConditions#ifNoneMatch()} set. */ public Mono delete(ContainerAccessConditions accessConditions) { - return containerAsyncRawClient - .delete(accessConditions) + accessConditions = accessConditions == null ? new ContainerAccessConditions() : accessConditions; + + if (!validateNoEtag(accessConditions.modifiedAccessConditions())) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException("ETag access conditions are not supported for this API."); + } + + return postProcessResponse(this.azureBlobStorage.containers() + .deleteWithRestResponseAsync(null, null, null, + accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) .map(VoidResponse::new); } @@ -322,8 +313,7 @@ public Mono delete(ContainerAccessConditions accessConditions) { * Returns the container's metadata and system properties. For more information, see the * Azure Docs. * - * @return - * A reactive response containing the container properties. + * @return A reactive response containing the container properties. */ public Mono> getProperties() { return this.getProperties(null); @@ -333,16 +323,14 @@ public Mono> getProperties() { * Returns the container's metadata and system properties. For more information, see the * Azure Docs. * - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return - * A reactive response containing the container properties. + * @param leaseAccessConditions By setting lease access conditions, requests will fail if the provided lease does + * not match the active lease on the blob. + * @return A reactive response containing the container properties. */ public Mono> getProperties(LeaseAccessConditions leaseAccessConditions) { - return containerAsyncRawClient - .getProperties(leaseAccessConditions) + return postProcessResponse(this.azureBlobStorage.containers() + .getPropertiesWithRestResponseAsync(null, null, null, + leaseAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new ContainerProperties(rb.deserializedHeaders()))); } @@ -350,11 +338,8 @@ public Mono> getProperties(LeaseAccessConditions l * Sets the container's metadata. For more information, see the * Azure Docs. * - * @param metadata - * {@link Metadata} - * - * @return - * A reactive response signalling completion. + * @param metadata {@link Metadata} + * @return A reactive response signalling completion. */ public Mono setMetadata(Metadata metadata) { return this.setMetadata(metadata, null); @@ -364,18 +349,27 @@ public Mono setMetadata(Metadata metadata) { * Sets the container's metadata. For more information, see the * Azure Docs. * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return - * A reactive response signalling completion. + * @param metadata {@link Metadata} + * @param accessConditions {@link ContainerAccessConditions} + * @return A reactive response signalling completion. + * @throws UnsupportedOperationException If {@link ContainerAccessConditions#modifiedAccessConditions()} has + * anything set other than {@link ModifiedAccessConditions#ifModifiedSince()}. */ public Mono setMetadata(Metadata metadata, - ContainerAccessConditions accessConditions) { - return containerAsyncRawClient - .setMetadata(metadata, accessConditions) + ContainerAccessConditions accessConditions) { + metadata = metadata == null ? new Metadata() : metadata; + accessConditions = accessConditions == null ? new ContainerAccessConditions() : accessConditions; + if (!validateNoEtag(accessConditions.modifiedAccessConditions()) + || accessConditions.modifiedAccessConditions().ifUnmodifiedSince() != null) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException( + "If-Modified-Since is the only HTTP access condition supported for this API"); + } + + return postProcessResponse(this.azureBlobStorage.containers() + .setMetadataWithRestResponseAsync(null, null, metadata, null, + accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) .map(VoidResponse::new); } @@ -384,8 +378,7 @@ public Mono setMetadata(Metadata metadata, * For more information, see the * Azure Docs. * - * @return - * A reactive response containing the container access policy. + * @return A reactive response containing the container access policy. */ public Mono> getAccessPolicy() { return this.getAccessPolicy(null); @@ -396,15 +389,13 @@ public Mono> getAccessPolicy() { * For more information, see the * Azure Docs. * - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return - * A reactive response containing the container access policy. + * @param leaseAccessConditions By setting lease access conditions, requests will fail if the provided lease does + * not match the active lease on the blob. + * @return A reactive response containing the container access policy. */ public Mono> getAccessPolicy(LeaseAccessConditions leaseAccessConditions) { - return containerAsyncRawClient.getAccessPolicy(leaseAccessConditions); + return postProcessResponse(this.azureBlobStorage.containers().getAccessPolicyWithRestResponseAsync(null, null, null, leaseAccessConditions, Context.NONE) + .map(response -> new SimpleResponse<>(response, new ContainerAccessPolicies(response.deserializedHeaders().blobPublicAccess(), response.value())))); } /** @@ -413,19 +404,16 @@ public Mono> getAccessPolicy(LeaseAccessCondit * ensure the time formatting is compatible with the service. For more information, see the * Azure Docs. * - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param identifiers - * A list of {@link SignedIdentifier} objects that specify the permissions for the container. Please see - * here - * for more information. Passing null will clear all access policies. - * - * @return - * A reactive response signalling completion. + * @param accessType Specifies how the data in this container is available to the public. See the + * x-ms-blob-public-access header in the Azure Docs for more information. Pass null for no public access. + * @param identifiers A list of {@link SignedIdentifier} objects that specify the permissions for the container. + * Please see + * here + * for more information. Passing null will clear all access policies. + * @return A reactive response signalling completion. */ public Mono setAccessPolicy(PublicAccessType accessType, - List identifiers) { + List identifiers) { return this.setAccessPolicy(accessType, identifiers, null); } @@ -435,79 +423,129 @@ public Mono setAccessPolicy(PublicAccessType accessType, * ensure the time formatting is compatible with the service. For more information, see the * Azure Docs. * - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param identifiers - * A list of {@link SignedIdentifier} objects that specify the permissions for the container. Please see - * here - * for more information. Passing null will clear all access policies. - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return - * A reactive response signalling completion. + * @param accessType Specifies how the data in this container is available to the public. See the + * x-ms-blob-public-access header in the Azure Docs for more information. Pass null for no public access. + * @param identifiers A list of {@link SignedIdentifier} objects that specify the permissions for the container. + * Please see + * here + * for more information. Passing null will clear all access policies. + * @param accessConditions {@link ContainerAccessConditions} + * @return A reactive response signalling completion. + * @throws UnsupportedOperationException If {@link ContainerAccessConditions#modifiedAccessConditions()} has either + * {@link ModifiedAccessConditions#ifMatch()} or {@link ModifiedAccessConditions#ifNoneMatch()} set. */ public Mono setAccessPolicy(PublicAccessType accessType, - List identifiers, ContainerAccessConditions accessConditions) { - return containerAsyncRawClient - .setAccessPolicy(accessType, identifiers, accessConditions) + List identifiers, ContainerAccessConditions accessConditions) { + accessConditions = accessConditions == null ? new ContainerAccessConditions() : accessConditions; + + if (!validateNoEtag(accessConditions.modifiedAccessConditions())) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException("ETag access conditions are not supported for this API."); + } + + /* + We truncate to seconds because the service only supports nanoseconds or seconds, but doing an + OffsetDateTime.now will only give back milliseconds (more precise fields are zeroed and not serialized). This + allows for proper serialization with no real detriment to users as sub-second precision on active time for + signed identifiers is not really necessary. + */ + if (identifiers != null) { + for (SignedIdentifier identifier : identifiers) { + if (identifier.accessPolicy() != null && identifier.accessPolicy().start() != null) { + identifier.accessPolicy().start( + identifier.accessPolicy().start().truncatedTo(ChronoUnit.SECONDS)); + } + if (identifier.accessPolicy() != null && identifier.accessPolicy().expiry() != null) { + identifier.accessPolicy().expiry( + identifier.accessPolicy().expiry().truncatedTo(ChronoUnit.SECONDS)); + } + } + } + + return postProcessResponse(this.azureBlobStorage.containers() + .setAccessPolicyWithRestResponseAsync(null, identifiers, null, accessType, + null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), + Context.NONE)) .map(VoidResponse::new); } /** - * Returns a reactive Publisher emitting all the blobs in this container lazily as needed. - * The directories are flattened and only actual blobs and no directories are returned. + * Returns a reactive Publisher emitting all the blobs in this container lazily as needed. The directories are + * flattened and only actual blobs and no directories are returned. * *

* Blob names are returned in lexicographic order. For more information, see the * Azure Docs. * *

- * E.g. listing a container containing a 'foo' folder, which contains blobs 'foo1' and 'foo2', and a blob - * on the root level 'bar', will return + * E.g. listing a container containing a 'foo' folder, which contains blobs 'foo1' and 'foo2', and a blob on the + * root level 'bar', will return * *

    - *
  • foo/foo1 - *
  • foo/foo2 - *
  • bar + *
  • foo/foo1 + *
  • foo/foo2 + *
  • bar *
* - * @return - * A reactive response emitting the flattened blobs. + * @return A reactive response emitting the flattened blobs. */ public Flux listBlobsFlat() { return this.listBlobsFlat(new ListBlobsOptions()); } /** - * Returns a reactive Publisher emitting all the blobs in this container lazily as needed. - * The directories are flattened and only actual blobs and no directories are returned. + * Returns a reactive Publisher emitting all the blobs in this container lazily as needed. The directories are + * flattened and only actual blobs and no directories are returned. * *

* Blob names are returned in lexicographic order. For more information, see the * Azure Docs. * *

- * E.g. listing a container containing a 'foo' folder, which contains blobs 'foo1' and 'foo2', and a blob - * on the root level 'bar', will return + * E.g. listing a container containing a 'foo' folder, which contains blobs 'foo1' and 'foo2', and a blob on the + * root level 'bar', will return * *

    - *
  • foo/foo1 - *
  • foo/foo2 - *
  • bar + *
  • foo/foo1 + *
  • foo/foo2 + *
  • bar *
* + * @param options {@link ListBlobsOptions} + * @return A reactive response emitting the listed blobs, flattened. + */ + public Flux listBlobsFlat(ListBlobsOptions options) { + return listBlobsFlatSegment(null, options).flatMapMany(response -> listBlobsFlatHelper(options, response)); + } + + /* + * Returns a single segment of blobs starting from the specified Marker. Use an empty + * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. + * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned + * Marker) to get the next segment. For more information, see the + * Azure Docs. + * + * @param marker + * Identifies the portion of the list to be returned with the next list operation. + * This value is returned in the response of a previous list operation as the + * ListBlobsFlatSegmentResponse.body().nextMarker(). Set to null to list the first segment. * @param options * {@link ListBlobsOptions} * - * @return - * A reactive response emitting the listed blobs, flattened. + * @return Emits the successful response. + * + * @apiNote ## Sample Code \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat "Sample code for ContainerAsyncClient.listBlobsFlatSegment")] \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat_helper "helper code for ContainerAsyncClient.listBlobsFlatSegment")] \n + * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) */ - public Flux listBlobsFlat(ListBlobsOptions options) { - return containerAsyncRawClient - .listBlobsFlatSegment(null, options) - .flatMapMany(response -> listBlobsFlatHelper(options, response)); + private Mono listBlobsFlatSegment(String marker, ListBlobsOptions options) { + options = options == null ? new ListBlobsOptions() : options; + + return postProcessResponse(this.azureBlobStorage.containers() + .listBlobFlatSegmentWithRestResponseAsync(null, options.prefix(), marker, + options.maxResults(), options.details().toList(), null, null, Context.NONE)); } private Flux listBlobsFlatHelper(ListBlobsOptions options, ContainersListBlobFlatSegmentResponse response) { @@ -521,7 +559,7 @@ private Flux listBlobsFlatHelper(ListBlobsOptions options, ContainersL if (response.value().nextMarker() != null) { // Recursively add the continuation items to the observable. - result = result.concatWith(containerAsyncRawClient.listBlobsFlatSegment(response.value().nextMarker(), options) + result = result.concatWith(listBlobsFlatSegment(response.value().nextMarker(), options) .flatMapMany(r -> listBlobsFlatHelper(options, r))); } @@ -529,80 +567,109 @@ private Flux listBlobsFlatHelper(ListBlobsOptions options, ContainersL } /** - * Returns a reactive Publisher emitting all the blobs and directories (prefixes) under - * the given directory (prefix). Directories will have {@link BlobItem#isPrefix()} set to - * true. + * Returns a reactive Publisher emitting all the blobs and directories (prefixes) under the given directory + * (prefix). Directories will have {@link BlobItem#isPrefix()} set to true. * *

* Blob names are returned in lexicographic order. For more information, see the * Azure Docs. * *

- * E.g. listing a container containing a 'foo' folder, which contains blobs 'foo1' and 'foo2', and a blob - * on the root level 'bar', will return the following results when prefix=null: + * E.g. listing a container containing a 'foo' folder, which contains blobs 'foo1' and 'foo2', and a blob on the + * root level 'bar', will return the following results when prefix=null: * *

    - *
  • foo/ (isPrefix = true) - *
  • bar (isPrefix = false) + *
  • foo/ (isPrefix = true) + *
  • bar (isPrefix = false) *
*

* will return the following results when prefix="foo/": * *

    - *
  • foo/foo1 (isPrefix = false) - *
  • foo/foo2 (isPrefix = false) + *
  • foo/foo1 (isPrefix = false) + *
  • foo/foo2 (isPrefix = false) *
* - * @param directory - * The directory to list blobs underneath - * - * @return - * A reactive response emitting the prefixes and blobs. + * @param directory The directory to list blobs underneath + * @return A reactive response emitting the prefixes and blobs. */ public Flux listBlobsHierarchy(String directory) { return this.listBlobsHierarchy("/", new ListBlobsOptions().prefix(directory)); } /** - * Returns a reactive Publisher emitting all the blobs and prefixes (directories) under - * the given prefix (directory). Directories will have {@link BlobItem#isPrefix()} set to - * true. + * Returns a reactive Publisher emitting all the blobs and prefixes (directories) under the given prefix + * (directory). Directories will have {@link BlobItem#isPrefix()} set to true. * *

* Blob names are returned in lexicographic order. For more information, see the * Azure Docs. * *

- * E.g. listing a container containing a 'foo' folder, which contains blobs 'foo1' and 'foo2', and a blob - * on the root level 'bar', will return the following results when prefix=null: + * E.g. listing a container containing a 'foo' folder, which contains blobs 'foo1' and 'foo2', and a blob on the + * root level 'bar', will return the following results when prefix=null: * *

    - *
  • foo/ (isPrefix = true) - *
  • bar (isPrefix = false) + *
  • foo/ (isPrefix = true) + *
  • bar (isPrefix = false) *
*

* will return the following results when prefix="foo/": * *

    - *
  • foo/foo1 (isPrefix = false) - *
  • foo/foo2 (isPrefix = false) + *
  • foo/foo1 (isPrefix = false) + *
  • foo/foo2 (isPrefix = false) *
* + * @param delimiter The delimiter for blob hierarchy, "/" for hierarchy based on directories + * @param options {@link ListBlobsOptions} + * @return A reactive response emitting the prefixes and blobs. + */ + public Flux listBlobsHierarchy(String delimiter, ListBlobsOptions options) { + return listBlobsHierarchySegment(null, delimiter, options) + .flatMapMany(response -> listBlobsHierarchyHelper(delimiter, options, Context.NONE, response)); + } + + /* + * Returns a single segment of blobs and blob prefixes starting from the specified Marker. Use an empty + * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. + * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned + * Marker) to get the next segment. For more information, see the + * Azure Docs. + * + * @param marker + * Identifies the portion of the list to be returned with the next list operation. + * This value is returned in the response of a previous list operation as the + * ListBlobsHierarchySegmentResponse.body().nextMarker(). Set to null to list the first segment. * @param delimiter - * The delimiter for blob hierarchy, "/" for hierarchy based on directories + * The operation returns a BlobPrefix element in the response body that acts as a placeholder for all blobs + * whose names begin with the same substring up to the appearance of the delimiter character. The delimiter may + * be a single character or a string. * @param options * {@link ListBlobsOptions} * - * @return - * A reactive response emitting the prefixes and blobs. + * @return Emits the successful response. + * @throws UnsupportedOperationException If {@link ListBlobsOptions#details()} has {@link BlobListDetails#snapshots()} + * set. + * + * @apiNote ## Sample Code \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy "Sample code for ContainerAsyncClient.listBlobsHierarchySegment")] \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy_helper "helper code for ContainerAsyncClient.listBlobsHierarchySegment")] \n + * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) */ - public Flux listBlobsHierarchy(String delimiter, ListBlobsOptions options) { - return containerAsyncRawClient.listBlobsHierarchySegment(null, delimiter, options) - .flatMapMany(response -> listBlobsHierarchyHelper(delimiter, options, Context.NONE, response)); + private Mono listBlobsHierarchySegment(String marker, String delimiter, ListBlobsOptions options) { + options = options == null ? new ListBlobsOptions() : options; + if (options.details().snapshots()) { + throw new UnsupportedOperationException("Including snapshots in a hierarchical listing is not supported."); + } + + return postProcessResponse(this.azureBlobStorage.containers() + .listBlobHierarchySegmentWithRestResponseAsync(null, delimiter, options.prefix(), marker, + options.maxResults(), options.details().toList(), null, null, Context.NONE)); } private Flux listBlobsHierarchyHelper(String delimiter, ListBlobsOptions options, - Context context, ContainersListBlobHierarchySegmentResponse response) { + Context context, ContainersListBlobHierarchySegmentResponse response) { Flux blobs; Flux prefixes; BlobHierarchyListSegment segment = response.value().segment(); @@ -620,7 +687,7 @@ private Flux listBlobsHierarchyHelper(String delimiter, ListBlobsOptio if (response.value().nextMarker() != null) { // Recursively add the continuation items to the observable. - result = result.concatWith(containerAsyncRawClient.listBlobsHierarchySegment(response.value().nextMarker(), delimiter, options) + result = result.concatWith(listBlobsHierarchySegment(response.value().nextMarker(), delimiter, options) .flatMapMany(r -> listBlobsHierarchyHelper(delimiter, options, context, r))); } @@ -699,14 +766,10 @@ private Flux listBlobsHierarchyHelper(String delimiter, ListBlobsOptio * Acquires a lease on the blob for write and delete operations. The lease duration must be between 15 to 60 * seconds, or infinite (-1). * - * @param proposedId - * A {@code String} in any valid GUID format. May be null. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that - * never expires. A non-infinite lease can be between 15 and 60 seconds. - * - * @return - * A reactive response containing the lease ID. + * @param proposedId A {@code String} in any valid GUID format. May be null. + * @param duration The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. A + * non-infinite lease can be between 15 and 60 seconds. + * @return A reactive response containing the lease ID. */ public Mono> acquireLease(String proposedId, int duration) { return this.acquireLease(proposedId, duration, null); @@ -716,33 +779,34 @@ public Mono> acquireLease(String proposedId, int duration) { * Acquires a lease on the blob for write and delete operations. The lease duration must be between 15 to 60 * seconds, or infinite (-1). * - * @param proposedID - * A {@code String} in any valid GUID format. May be null. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that - * never expires. A non-infinite lease can be between 15 and 60 seconds. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return - * A reactive response containing the lease ID. + * @param proposedID A {@code String} in any valid GUID format. May be null. + * @param duration The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. A + * non-infinite lease can be between 15 and 60 seconds. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @return A reactive response containing the lease ID. + * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or {@link + * ModifiedAccessConditions#ifNoneMatch()} is set. */ public Mono> acquireLease(String proposedID, int duration, ModifiedAccessConditions modifiedAccessConditions) { - return containerAsyncRawClient - .acquireLease(proposedID, duration, modifiedAccessConditions) + if (!this.validateNoEtag(modifiedAccessConditions)) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException( + "ETag access conditions are not supported for this API."); + } + + return postProcessResponse(this.azureBlobStorage.containers().acquireLeaseWithRestResponseAsync( + null, null, duration, proposedID, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseId())); } /** * Renews the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * - * @return - * A reactive response containing the renewed lease ID. + * @param leaseID The leaseId of the active lease on the blob. + * @return A reactive response containing the renewed lease ID. */ public Mono> renewLease(String leaseID) { return this.renewLease(leaseID, null); @@ -751,30 +815,32 @@ public Mono> renewLease(String leaseID) { /** * Renews the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return - * A reactive response containing the renewed lease ID. + * @param leaseID The leaseId of the active lease on the blob. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @return A reactive response containing the renewed lease ID. + * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or {@link + * ModifiedAccessConditions#ifNoneMatch()} is set. */ public Mono> renewLease(String leaseID, ModifiedAccessConditions modifiedAccessConditions) { - return containerAsyncRawClient - .renewLease(leaseID, modifiedAccessConditions) + if (!this.validateNoEtag(modifiedAccessConditions)) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException( + "ETag access conditions are not supported for this API."); + } + + return postProcessResponse(this.azureBlobStorage.containers().renewLeaseWithRestResponseAsync(null, + leaseID, null, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseId())); } /** * Releases the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * - * @return - * A reactive response signalling completion. + * @param leaseID The leaseId of the active lease on the blob. + * @return A reactive response signalling completion. */ public Mono releaseLease(String leaseID) { return this.releaseLease(leaseID, null); @@ -783,19 +849,24 @@ public Mono releaseLease(String leaseID) { /** * Releases the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return - * A reactive response signalling completion. + * @param leaseID The leaseId of the active lease on the blob. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @return A reactive response signalling completion. + * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or {@link + * ModifiedAccessConditions#ifNoneMatch()} is set. */ public Mono releaseLease(String leaseID, ModifiedAccessConditions modifiedAccessConditions) { - return containerAsyncRawClient - .releaseLease(leaseID, modifiedAccessConditions) + if (!this.validateNoEtag(modifiedAccessConditions)) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException( + "ETag access conditions are not supported for this API."); + } + + return postProcessResponse(this.azureBlobStorage.containers().releaseLeaseWithRestResponseAsync( + null, leaseID, null, null, modifiedAccessConditions, Context.NONE)) .map(VoidResponse::new); } @@ -803,8 +874,7 @@ public Mono releaseLease(String leaseID, ModifiedAccessConditions * BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1) constant * to break a fixed-duration lease when it expires or an infinite lease immediately. * - * @return - * A reactive response containing the remaining time in the broken lease. + * @return A reactive response containing the remaining time in the broken lease. */ public Mono> breakLease() { return this.breakLease(null, null); @@ -814,58 +884,65 @@ public Mono> breakLease() { * BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1) constant * to break a fixed-duration lease when it expires or an infinite lease immediately. * - * @param breakPeriodInSeconds - * An optional {@code Integer} representing the proposed duration of seconds that the lease should continue - * before it is broken, between 0 and 60 seconds. This break period is only used if it is shorter than the - * time remaining on the lease. If longer, the time remaining on the lease is used. A new lease will not be - * available before the break period has expired, but the lease may be held for longer than the break - * period. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return - * A reactive response containing the remaining time in the broken lease. + * @param breakPeriodInSeconds An optional {@code Integer} representing the proposed duration of seconds that the + * lease should continue before it is broken, between 0 and 60 seconds. This break period is only used if it is + * shorter than the time remaining on the lease. If longer, the time remaining on the lease is used. A new lease + * will not be available before the break period has expired, but the lease may be held for longer than the break + * period. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @return A reactive response containing the remaining time in the broken lease. + * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or {@link + * ModifiedAccessConditions#ifNoneMatch()} is set. */ public Mono> breakLease(Integer breakPeriodInSeconds, ModifiedAccessConditions modifiedAccessConditions) { - return containerAsyncRawClient - .breakLease(breakPeriodInSeconds, modifiedAccessConditions) + if (!this.validateNoEtag(modifiedAccessConditions)) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException( + "ETag access conditions are not supported for this API."); + } + + return postProcessResponse(this.azureBlobStorage.containers().breakLeaseWithRestResponseAsync(null, + null, breakPeriodInSeconds, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, Duration.ofSeconds(rb.deserializedHeaders().leaseTime()))); } /** * ChangeLease changes the blob's lease ID. * - * @param leaseId - * The leaseId of the active lease on the blob. - * @param proposedID - * A {@code String} in any valid GUID format. - * - * @return - * A reactive response containing the new lease ID. + * @param leaseId The leaseId of the active lease on the blob. + * @param proposedID A {@code String} in any valid GUID format. + * @return A reactive response containing the new lease ID. */ public Mono> changeLease(String leaseId, String proposedID) { return this.changeLease(leaseId, proposedID, null); } /** - * ChangeLease changes the blob's lease ID. For more information, see the Azure Docs. - * - * @param leaseId - * The leaseId of the active lease on the blob. - * @param proposedID - * A {@code String} in any valid GUID format. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * + * ChangeLease changes the blob's lease ID. For more information, see the Azure + * Docs. + * + * @param leaseId The leaseId of the active lease on the blob. + * @param proposedID A {@code String} in any valid GUID format. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. * @return A reactive response containing the new lease ID. + * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or {@link + * ModifiedAccessConditions#ifNoneMatch()} is set. */ public Mono> changeLease(String leaseId, String proposedID, ModifiedAccessConditions modifiedAccessConditions) { - return containerAsyncRawClient - .changeLease(leaseId, proposedID, modifiedAccessConditions) + if (!this.validateNoEtag(modifiedAccessConditions)) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new UnsupportedOperationException( + "ETag access conditions are not supported for this API."); + } + + return postProcessResponse(this.azureBlobStorage.containers().changeLeaseWithRestResponseAsync(null, + leaseId, proposedID, null, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().leaseId())); } @@ -873,63 +950,52 @@ public Mono> changeLease(String leaseId, String proposedID, Mod * Returns the sku name and account kind for the account. For more information, please see the * Azure Docs. * - * @return - * A reactive response containing the account info. + * @return A reactive response containing the account info. */ public Mono> getAccountInfo() { - return containerAsyncRawClient - .getAccountInfo() + return postProcessResponse( + this.azureBlobStorage.containers().getAccountInfoWithRestResponseAsync(null, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new StorageAccountInfo(rb.deserializedHeaders()))); } + private boolean validateNoEtag(ModifiedAccessConditions modifiedAccessConditions) { + if (modifiedAccessConditions == null) { + return true; + } + return modifiedAccessConditions.ifMatch() == null && modifiedAccessConditions.ifNoneMatch() == null; + } + /** * Generates a user delegation SAS with the specified parameters * - * @param userDelegationKey - * The {@code UserDelegationKey} user delegation key for the SAS - * @param accountName - * The {@code String} account name for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * - * @return - * A string that represents the SAS token + * @param userDelegationKey The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName The {@code String} account name for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @return A string that represents the SAS token */ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, - ContainerSASPermission permissions, OffsetDateTime expiryTime) { - return this.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, null /* - startTime */, null /* version */, null /* sasProtocol */, null /* ipRange */, null /* cacheControl */, null - /* contentDisposition */, null /* contentEncoding */, null /* contentLanguage */, null /* contentType */); + ContainerSASPermission permissions, OffsetDateTime expiryTime) { + return this.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, null, null, + null, null, null, null, null, null, null); } /** * Generates a user delegation SAS token with the specified parameters * - * @param userDelegationKey - * The {@code UserDelegationKey} user delegation key for the SAS - * @param accountName - * The {@code String} account name for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * - * @return - * A string that represents the SAS token + * @param userDelegationKey The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName The {@code String} account name for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @return A string that represents the SAS token */ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, - ContainerSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, - SASProtocol sasProtocol, IPRange ipRange) { + ContainerSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange) { return this.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, startTime, version, sasProtocol, ipRange, null /* cacheControl */, null /* contentDisposition */, null /* contentEncoding */, null /* contentLanguage */, null /* contentType */); @@ -938,40 +1004,25 @@ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, Str /** * Generates a user delegation SAS token with the specified parameters * - * @param userDelegationKey - * The {@code UserDelegationKey} user delegation key for the SAS - * @param accountName - * The {@code String} account name for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * @param cacheControl - * An optional {@code String} cache-control header for the SAS. - * @param contentDisposition - * An optional {@code String} content-disposition header for the SAS. - * @param contentEncoding - * An optional {@code String} content-encoding header for the SAS. - * @param contentLanguage - * An optional {@code String} content-language header for the SAS. - * @param contentType - * An optional {@code String} content-type header for the SAS. - * - * @return - * A string that represents the SAS token + * @param userDelegationKey The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName The {@code String} account name for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @param cacheControl An optional {@code String} cache-control header for the SAS. + * @param contentDisposition An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage An optional {@code String} content-language header for the SAS. + * @param contentType An optional {@code String} content-type header for the SAS. + * @return A string that represents the SAS token */ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, - ContainerSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, - SASProtocol sasProtocol, IPRange ipRange, String cacheControl, String contentDisposition, - String contentEncoding, String contentLanguage, String contentType) { + ContainerSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange, String cacheControl, String contentDisposition, + String contentEncoding, String contentLanguage, String contentType) { ServiceSASSignatureValues serviceSASSignatureValues = new ServiceSASSignatureValues(version, sasProtocol, startTime, expiryTime, permissions == null ? null : permissions.toString(), ipRange, null /* identifier*/, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); @@ -986,13 +1037,9 @@ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, Str /** * Generates a SAS token with the specified parameters * - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * - * @return - * A string that represents the SAS token + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @return A string that represents the SAS token */ public String generateSAS(ContainerSASPermission permissions, OffsetDateTime expiryTime) { return this.generateSAS(null, permissions, /* identifier */ expiryTime, null /* startTime */, null /* version @@ -1003,11 +1050,8 @@ public String generateSAS(ContainerSASPermission permissions, OffsetDateTime exp /** * Generates a SAS token with the specified parameters * - * @param identifier - * The {@code String} name of the access policy on the container this SAS references if any - * - * @return - * A string that represents the SAS token + * @param identifier The {@code String} name of the access policy on the container this SAS references if any + * @return A string that represents the SAS token */ public String generateSAS(String identifier) { return this.generateSAS(identifier, null /* permissions*/, null /* expiryTime */, null /* startTime */, null @@ -1018,27 +1062,18 @@ public String generateSAS(String identifier) { /** * Generates a SAS token with the specified parameters * - * @param identifier - * The {@code String} name of the access policy on the container this SAS references if any - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * - * @return - * A string that represents the SAS token + * @param identifier The {@code String} name of the access policy on the container this SAS references if any + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @return A string that represents the SAS token */ public String generateSAS(String identifier, ContainerSASPermission permissions, OffsetDateTime expiryTime, - OffsetDateTime startTime, - String version, SASProtocol sasProtocol, IPRange ipRange) { + OffsetDateTime startTime, + String version, SASProtocol sasProtocol, IPRange ipRange) { return this.generateSAS(identifier, permissions, expiryTime, startTime, version, sasProtocol, ipRange, null /* cacheControl */, null /* contentDisposition */, null /* contentEncoding */, null /* contentLanguage */, null /*contentType*/); @@ -1047,43 +1082,29 @@ public String generateSAS(String identifier, ContainerSASPermission permissions, /** * Generates a SAS token with the specified parameters * - * @param identifier - * The {@code String} name of the access policy on the container this SAS references if any - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * @param cacheControl - * An optional {@code String} cache-control header for the SAS. - * @param contentDisposition - * An optional {@code String} content-disposition header for the SAS. - * @param contentEncoding - * An optional {@code String} content-encoding header for the SAS. - * @param contentLanguage - * An optional {@code String} content-language header for the SAS. - * @param contentType - * An optional {@code String} content-type header for the SAS. - * - * @return - * A string that represents the SAS token + * @param identifier The {@code String} name of the access policy on the container this SAS references if any + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @param cacheControl An optional {@code String} cache-control header for the SAS. + * @param contentDisposition An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage An optional {@code String} content-language header for the SAS. + * @param contentType An optional {@code String} content-type header for the SAS. + * @return A string that represents the SAS token */ public String generateSAS(String identifier, ContainerSASPermission permissions, OffsetDateTime expiryTime, - OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange, String cacheControl, - String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange, String cacheControl, + String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { ServiceSASSignatureValues serviceSASSignatureValues = new ServiceSASSignatureValues(version, sasProtocol, startTime, expiryTime, permissions == null ? null : permissions.toString(), ipRange, identifier, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); SharedKeyCredential sharedKeyCredential = - Utility.getSharedKeyCredential(this.containerAsyncRawClient.azureBlobStorage.httpPipeline()); + Utility.getSharedKeyCredential(this.azureBlobStorage.httpPipeline()); Utility.assertNotNull("sharedKeyCredential", sharedKeyCredential); @@ -1100,7 +1121,7 @@ public String generateSAS(String identifier, ContainerSASPermission permissions, */ private ServiceSASSignatureValues configureServiceSASSignatureValues(ServiceSASSignatureValues serviceSASSignatureValues, String accountName) { // Set canonical name - serviceSASSignatureValues.canonicalName(this.containerAsyncRawClient.azureBlobStorage.url(), accountName); + serviceSASSignatureValues.canonicalName(this.azureBlobStorage.url(), accountName); // Set snapshotId to null serviceSASSignatureValues.snapshotId(null); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncRawClient.java deleted file mode 100644 index e4106549b208c..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerAsyncRawClient.java +++ /dev/null @@ -1,708 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.http.rest.Response; -import com.azure.core.http.rest.SimpleResponse; -import com.azure.core.util.Context; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.BlobListDetails; -import com.azure.storage.blob.models.ContainerAccessConditions; -import com.azure.storage.blob.models.ContainerAccessPolicies; -import com.azure.storage.blob.models.ContainersAcquireLeaseResponse; -import com.azure.storage.blob.models.ContainersBreakLeaseResponse; -import com.azure.storage.blob.models.ContainersChangeLeaseResponse; -import com.azure.storage.blob.models.ContainersCreateResponse; -import com.azure.storage.blob.models.ContainersDeleteResponse; -import com.azure.storage.blob.models.ContainersGetAccountInfoResponse; -import com.azure.storage.blob.models.ContainersGetPropertiesResponse; -import com.azure.storage.blob.models.ContainersListBlobFlatSegmentResponse; -import com.azure.storage.blob.models.ContainersListBlobHierarchySegmentResponse; -import com.azure.storage.blob.models.ContainersReleaseLeaseResponse; -import com.azure.storage.blob.models.ContainersRenewLeaseResponse; -import com.azure.storage.blob.models.ContainersSetAccessPolicyResponse; -import com.azure.storage.blob.models.ContainersSetMetadataResponse; -import com.azure.storage.blob.models.LeaseAccessConditions; -import com.azure.storage.blob.models.ListBlobsOptions; -import com.azure.storage.blob.models.Metadata; -import com.azure.storage.blob.models.ModifiedAccessConditions; -import com.azure.storage.blob.models.PublicAccessType; -import com.azure.storage.blob.models.SignedIdentifier; -import reactor.core.publisher.Mono; - -import java.time.temporal.ChronoUnit; -import java.util.List; - -import static com.azure.storage.blob.Utility.postProcessResponse; - -/** - * Represents a URL to a container. It may be obtained by direct construction or via the create method on a - * {@link StorageAsyncRawClient} object. This class does not hold any state about a particular blob but is instead a convenient way - * of sending off appropriate requests to the resource on the service. It may also be used to construct URLs to blobs. - * Please refer to the - * Azure Docs - * for more information on containers. - */ -final class ContainerAsyncRawClient { - - public static final String ROOT_CONTAINER_NAME = "$root"; - - public static final String STATIC_WEBSITE_CONTAINER_NAME = "$web"; - - public static final String LOG_CONTAINER_NAME = "$logs"; - - AzureBlobStorageImpl azureBlobStorage; - - /** - * Creates a {@code ContainerAsyncClient} object pointing to the account specified by the URL and using the provided - * pipeline to make HTTP requests. - */ - ContainerAsyncRawClient(AzureBlobStorageImpl azureBlobStorage) { - this.azureBlobStorage = azureBlobStorage; - } - - /** - * Creates a new container within a storage account. If a container with the same name already exists, the operation - * fails. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono create() { - return this.create(null, null); - } - - /** - * Creates a new container within a storage account. If a container with the same name already exists, the operation - * fails. For more information, see the - * Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono create(Metadata metadata, PublicAccessType accessType) { - metadata = metadata == null ? new Metadata() : metadata; - - return postProcessResponse(this.azureBlobStorage.containers().createWithRestResponseAsync( - null, null, metadata, accessType, null, Context.NONE)); - - } - - /** - * Marks the specified container for deletion. The container and any blobs contained within it are later - * deleted during garbage collection. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.delete")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono delete() { - return this.delete(null); - } - - /** - * Marks the specified container for deletion. The container and any blobs contained within it are later - * deleted during garbage collection. For more information, see the - * Azure Docs. - * - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If {@link ContainerAccessConditions#modifiedAccessConditions()} has either - * {@link ModifiedAccessConditions#ifMatch()} or {@link ModifiedAccessConditions#ifNoneMatch()} set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.delete")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono delete(ContainerAccessConditions accessConditions) { - accessConditions = accessConditions == null ? new ContainerAccessConditions() : accessConditions; - - if (!validateNoEtag(accessConditions.modifiedAccessConditions())) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException("ETag access conditions are not supported for this API."); - } - - return postProcessResponse(this.azureBlobStorage.containers() - .deleteWithRestResponseAsync(null, null, null, - accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Returns the container's metadata and system properties. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getProperties() { - return this.getProperties(null); - } - - /** - * Returns the container's metadata and system properties. For more information, see the - * Azure Docs. - * - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getProperties(LeaseAccessConditions leaseAccessConditions) { - return postProcessResponse(this.azureBlobStorage.containers() - .getPropertiesWithRestResponseAsync(null, null, null, - leaseAccessConditions, Context.NONE)); - } - - /** - * Sets the container's metadata. For more information, see the - * Azure Docs. - * - * @param metadata - * {@link Metadata} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.setMetadata")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setMetadata(Metadata metadata) { - return this.setMetadata(metadata, null); - } - - /** - * Sets the container's metadata. For more information, see the - * Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If {@link ContainerAccessConditions#modifiedAccessConditions()} has anything - * set other than {@link ModifiedAccessConditions#ifModifiedSince()}. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.setMetadata")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setMetadata(Metadata metadata, - ContainerAccessConditions accessConditions) { - metadata = metadata == null ? new Metadata() : metadata; - accessConditions = accessConditions == null ? new ContainerAccessConditions() : accessConditions; - if (!validateNoEtag(accessConditions.modifiedAccessConditions()) - || accessConditions.modifiedAccessConditions().ifUnmodifiedSince() != null) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException( - "If-Modified-Since is the only HTTP access condition supported for this API"); - } - - return postProcessResponse(this.azureBlobStorage.containers() - .setMetadataWithRestResponseAsync(null, null, metadata, null, - accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Returns the container's permissions. The permissions indicate whether container's blobs may be accessed publicly. - * For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.getAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono> getAccessPolicy() { - return this.getAccessPolicy(null); - } - - /** - * Returns the container's permissions. The permissions indicate whether container's blobs may be accessed publicly. - * For more information, see the - * Azure Docs. - * - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.getAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono> getAccessPolicy(LeaseAccessConditions leaseAccessConditions) { - return postProcessResponse(this.azureBlobStorage.containers().getAccessPolicyWithRestResponseAsync(null, null, null, leaseAccessConditions, Context.NONE) - .map(response -> new SimpleResponse<>(response, new ContainerAccessPolicies(response.deserializedHeaders().blobPublicAccess(), response.value())))); - } - - /** - * Sets the container's permissions. The permissions indicate whether blobs in a container may be accessed publicly. - * Note that, for each signed identifier, we will truncate the start and expiry times to the nearest second to - * ensure the time formatting is compatible with the service. For more information, see the - * Azure Docs. - * - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param identifiers - * A list of {@link SignedIdentifier} objects that specify the permissions for the container. Please see - * here - * for more information. Passing null will clear all access policies. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.setAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setAccessPolicy(PublicAccessType accessType, - List identifiers) { - return this.setAccessPolicy(accessType, identifiers, null); - } - - /** - * Sets the container's permissions. The permissions indicate whether blobs in a container may be accessed publicly. - * Note that, for each signed identifier, we will truncate the start and expiry times to the nearest second to - * ensure the time formatting is compatible with the service. For more information, see the - * Azure Docs. - * - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param identifiers - * A list of {@link SignedIdentifier} objects that specify the permissions for the container. Please see - * here - * for more information. Passing null will clear all access policies. - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If {@link ContainerAccessConditions#modifiedAccessConditions()} has either - * {@link ModifiedAccessConditions#ifMatch()} or {@link ModifiedAccessConditions#ifNoneMatch()} set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.setAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setAccessPolicy(PublicAccessType accessType, - List identifiers, ContainerAccessConditions accessConditions) { - accessConditions = accessConditions == null ? new ContainerAccessConditions() : accessConditions; - - if (!validateNoEtag(accessConditions.modifiedAccessConditions())) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException("ETag access conditions are not supported for this API."); - } - - /* - We truncate to seconds because the service only supports nanoseconds or seconds, but doing an - OffsetDateTime.now will only give back milliseconds (more precise fields are zeroed and not serialized). This - allows for proper serialization with no real detriment to users as sub-second precision on active time for - signed identifiers is not really necessary. - */ - if (identifiers != null) { - for (SignedIdentifier identifier : identifiers) { - if (identifier.accessPolicy() != null && identifier.accessPolicy().start() != null) { - identifier.accessPolicy().start( - identifier.accessPolicy().start().truncatedTo(ChronoUnit.SECONDS)); - } - if (identifier.accessPolicy() != null && identifier.accessPolicy().expiry() != null) { - identifier.accessPolicy().expiry( - identifier.accessPolicy().expiry().truncatedTo(ChronoUnit.SECONDS)); - } - } - } - - return postProcessResponse(this.azureBlobStorage.containers() - .setAccessPolicyWithRestResponseAsync(null, identifiers, null, accessType, - null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), - Context.NONE)); - - } - - private boolean validateNoEtag(ModifiedAccessConditions modifiedAccessConditions) { - if (modifiedAccessConditions == null) { - return true; - } - return modifiedAccessConditions.ifMatch() == null && modifiedAccessConditions.ifNoneMatch() == null; - } - - /** - * Acquires a lease on the container for delete operations. The lease duration must be between 15 to - * 60 seconds, or infinite (-1). For more information, see the - * Azure Docs. - * - * @apiNote - * ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - * - * @param proposedId - * A {@code String} in any valid GUID format. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. - * A non-infinite lease can be between 15 and 60 seconds. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono acquireLease(String proposedId, int duration) { - return this.acquireLease(proposedId, duration, null); - } - - /** - * Acquires a lease on the container for delete operations. The lease duration must be between 15 to - * 60 seconds, or infinite (-1). For more information, see the - * Azure Docs. - * - * @param proposedID - * A {@code String} in any valid GUID format. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. - * A non-infinite lease can be between 15 and 60 seconds. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or - * {@link ModifiedAccessConditions#ifNoneMatch()} is set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono acquireLease(String proposedID, int duration, - ModifiedAccessConditions modifiedAccessConditions) { - if (!this.validateNoEtag(modifiedAccessConditions)) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException( - "ETag access conditions are not supported for this API."); - } - - return postProcessResponse(this.azureBlobStorage.containers().acquireLeaseWithRestResponseAsync( - null, null, duration, proposedID, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * Renews the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.renewLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono renewLease(String leaseID) { - return this.renewLease(leaseID, null); - } - - /** - * Renews the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or - * {@link ModifiedAccessConditions#ifNoneMatch()} is set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.renewLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono renewLease(String leaseID, - ModifiedAccessConditions modifiedAccessConditions) { - if (!this.validateNoEtag(modifiedAccessConditions)) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException( - "ETag access conditions are not supported for this API."); - } - - return postProcessResponse(this.azureBlobStorage.containers().renewLeaseWithRestResponseAsync(null, - leaseID, null, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * Releases the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.releaseLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono releaseLease(String leaseID) { - return this.releaseLease(leaseID, null); - } - - /** - * Releases the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or - * {@link ModifiedAccessConditions#ifNoneMatch()} is set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.releaseLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono releaseLease(String leaseID, - ModifiedAccessConditions modifiedAccessConditions) { - if (!this.validateNoEtag(modifiedAccessConditions)) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException( - "ETag access conditions are not supported for this API."); - } - - return postProcessResponse(this.azureBlobStorage.containers().releaseLeaseWithRestResponseAsync( - null, leaseID, null, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * Breaks the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @apiNote - * ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.breakLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - * - * @return Emits the successful response. - */ - public Mono breakLease() { - return this.breakLease(null, null); - } - - /** - * Breaks the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param breakPeriodInSeconds - * An optional {@code Integer} representing the proposed duration of seconds that the lease should continue - * before it is broken, between 0 and 60 seconds. This break period is only used if it is shorter than the time - * remaining on the lease. If longer, the time remaining on the lease is used. A new lease will not be - * available before the break period has expired, but the lease may be held for longer than the break period. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or - * {@link ModifiedAccessConditions#ifNoneMatch()} is set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.breakLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono breakLease(Integer breakPeriodInSeconds, - ModifiedAccessConditions modifiedAccessConditions) { - if (!this.validateNoEtag(modifiedAccessConditions)) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException( - "ETag access conditions are not supported for this API."); - } - - return postProcessResponse(this.azureBlobStorage.containers().breakLeaseWithRestResponseAsync(null, - null, breakPeriodInSeconds, null, modifiedAccessConditions, Context.NONE)); - - } - - /** - * Changes the container's leaseAccessConditions. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param proposedID - * A {@code String} in any valid GUID format. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.changeLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono changeLease(String leaseID, String proposedID) { - return this.changeLease(leaseID, proposedID, null); - } - - /** - * Changes the container's leaseAccessConditions. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param proposedID - * A {@code String} in any valid GUID format. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If either {@link ModifiedAccessConditions#ifMatch()} or - * {@link ModifiedAccessConditions#ifNoneMatch()} is set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.changeLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono changeLease(String leaseID, String proposedID, - ModifiedAccessConditions modifiedAccessConditions) { - if (!this.validateNoEtag(modifiedAccessConditions)) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new UnsupportedOperationException( - "ETag access conditions are not supported for this API."); - } - - return postProcessResponse(this.azureBlobStorage.containers().changeLeaseWithRestResponseAsync(null, - leaseID, proposedID, null, null, modifiedAccessConditions, Context.NONE)); - } - - /** - * Returns a single segment of blobs starting from the specified Marker. Use an empty - * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. - * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned - * Marker) to get the next segment. For more information, see the - * Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListBlobsFlatSegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param options - * {@link ListBlobsOptions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat "Sample code for ContainerAsyncClient.listBlobsFlatSegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat_helper "helper code for ContainerAsyncClient.listBlobsFlatSegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono listBlobsFlatSegment(String marker, ListBlobsOptions options) { - options = options == null ? new ListBlobsOptions() : options; - - return postProcessResponse(this.azureBlobStorage.containers() - .listBlobFlatSegmentWithRestResponseAsync(null, options.prefix(), marker, - options.maxResults(), options.details().toList(), null, null, Context.NONE)); - } - - /** - * Returns a single segment of blobs and blob prefixes starting from the specified Marker. Use an empty - * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. - * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned - * Marker) to get the next segment. For more information, see the - * Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListBlobsHierarchySegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param delimiter - * The operation returns a BlobPrefix element in the response body that acts as a placeholder for all blobs - * whose names begin with the same substring up to the appearance of the delimiter character. The delimiter may - * be a single character or a string. - * @param options - * {@link ListBlobsOptions} - * - * @return Emits the successful response. - * @throws UnsupportedOperationException If {@link ListBlobsOptions#details()} has {@link BlobListDetails#snapshots()} - * set. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy "Sample code for ContainerAsyncClient.listBlobsHierarchySegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy_helper "helper code for ContainerAsyncClient.listBlobsHierarchySegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono listBlobsHierarchySegment(String marker, String delimiter, - ListBlobsOptions options) { - options = options == null ? new ListBlobsOptions() : options; - if (options.details().snapshots()) { - throw new UnsupportedOperationException("Including snapshots in a hierarchical listing is not supported."); - } - - return postProcessResponse(this.azureBlobStorage.containers() - .listBlobHierarchySegmentWithRestResponseAsync(null, delimiter, options.prefix(), marker, - options.maxResults(), options.details().toList(), null, null, Context.NONE)); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for ContainerAsyncClient.getAccountInfo")] \n - * For more samples, please see the [Samples file] (https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getAccountInfo() { - return postProcessResponse( - this.azureBlobStorage.containers().getAccountInfoWithRestResponseAsync(null, Context.NONE)); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClient.java index c9733fd3a7f61..de5d4f1431f39 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClient.java @@ -25,47 +25,45 @@ import java.util.List; /** - * Client to a container. It may only be instantiated through a {@link ContainerClientBuilder} or via the method - * {@link StorageClient#getContainerClient(String)}. This class does not hold any - * state about a particular container but is instead a convenient way of sending off appropriate requests to - * the resource on the service. It may also be used to construct URLs to blobs. + * Client to a container. It may only be instantiated through a {@link ContainerClientBuilder} or via the method {@link + * StorageClient#getContainerClient(String)}. This class does not hold any state about a particular container but is + * instead a convenient way of sending off appropriate requests to the resource on the service. It may also be used to + * construct URLs to blobs. * *

* This client contains operations on a container. Operations on a blob are available on {@link BlobClient} through * {@link #getBlobClient(String)}, and operations on the service are available on {@link StorageClient}. * *

- * Please refer to the Azure Docs - * for more information on containers. + * Please refer to the Azure + * Docs for more information on containers. */ public final class ContainerClient { - private ContainerAsyncClient containerAsyncClient; - public static final String ROOT_CONTAINER_NAME = "$root"; + public static final String ROOT_CONTAINER_NAME = ContainerAsyncClient.ROOT_CONTAINER_NAME; - public static final String STATIC_WEBSITE_CONTAINER_NAME = "$web"; + public static final String STATIC_WEBSITE_CONTAINER_NAME = ContainerAsyncClient.STATIC_WEBSITE_CONTAINER_NAME; - public static final String LOG_CONTAINER_NAME = "$logs"; + public static final String LOG_CONTAINER_NAME = ContainerAsyncClient.LOG_CONTAINER_NAME; /** * Package-private constructor for use by {@link ContainerClientBuilder}. + * * @param containerAsyncClient the async container client */ ContainerClient(ContainerAsyncClient containerAsyncClient) { - this.containerAsyncClient = containerAsyncClient; + this.containerAsyncClient = containerAsyncClient; } /** - * Creates a new {@link BlockBlobClient} object by concatenating the blobName to the end of - * ContainerAsyncClient's URL. The new BlockBlobClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the BlockBlobClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's NewBlockBlobAsyncClient instead of calling this object's - * NewBlockBlobAsyncClient method. - * - * @param blobName - * A {@code String} representing the name of the blob. + * Creates a new {@link BlockBlobClient} object by concatenating the blobName to the end of ContainerAsyncClient's + * URL. The new BlockBlobClient uses the same request policy pipeline as the ContainerAsyncClient. To change the + * pipeline, create the BlockBlobClient and then call its WithPipeline method passing in the desired pipeline + * object. Or, call this package's NewBlockBlobAsyncClient instead of calling this object's NewBlockBlobAsyncClient + * method. * + * @param blobName A {@code String} representing the name of the blob. * @return A new {@link BlockBlobClient} object which references the blob with the specified name in this container. */ public BlockBlobClient getBlockBlobClient(String blobName) { @@ -73,17 +71,14 @@ public BlockBlobClient getBlockBlobClient(String blobName) { } /** - * Creates a new {@link BlockBlobClient} object by concatenating the blobName to the end of - * ContainerAsyncClient's URL. The new BlockBlobClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the BlockBlobClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's NewBlockBlobAsyncClient instead of calling this object's - * NewBlockBlobAsyncClient method. - * - * @param blobName - * A {@code String} representing the name of the blob. - * @param snapshot - * the snapshot identifier for the blob. + * Creates a new {@link BlockBlobClient} object by concatenating the blobName to the end of ContainerAsyncClient's + * URL. The new BlockBlobClient uses the same request policy pipeline as the ContainerAsyncClient. To change the + * pipeline, create the BlockBlobClient and then call its WithPipeline method passing in the desired pipeline + * object. Or, call this package's NewBlockBlobAsyncClient instead of calling this object's NewBlockBlobAsyncClient + * method. * + * @param blobName A {@code String} representing the name of the blob. + * @param snapshot the snapshot identifier for the blob. * @return A new {@link BlockBlobClient} object which references the blob with the specified name in this container. */ public BlockBlobClient getBlockBlobClient(String blobName, String snapshot) { @@ -91,15 +86,12 @@ public BlockBlobClient getBlockBlobClient(String blobName, String snapshot) { } /** - * Creates creates a new PageBlobClient object by concatenating blobName to the end of - * ContainerAsyncClient's URL. The new PageBlobClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the PageBlobClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's NewPageBlobAsyncClient instead of calling this object's - * NewPageBlobAsyncClient method. - * - * @param blobName - * A {@code String} representing the name of the blob. + * Creates creates a new PageBlobClient object by concatenating blobName to the end of ContainerAsyncClient's URL. + * The new PageBlobClient uses the same request policy pipeline as the ContainerAsyncClient. To change the pipeline, + * create the PageBlobClient and then call its WithPipeline method passing in the desired pipeline object. Or, call + * this package's NewPageBlobAsyncClient instead of calling this object's NewPageBlobAsyncClient method. * + * @param blobName A {@code String} representing the name of the blob. * @return A new {@link PageBlobClient} object which references the blob with the specified name in this container. */ public PageBlobClient getPageBlobClient(String blobName) { @@ -107,17 +99,13 @@ public PageBlobClient getPageBlobClient(String blobName) { } /** - * Creates creates a new PageBlobClient object by concatenating blobName to the end of - * ContainerAsyncClient's URL. The new PageBlobClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the PageBlobClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's NewPageBlobAsyncClient instead of calling this object's - * NewPageBlobAsyncClient method. - * - * @param blobName - * A {@code String} representing the name of the blob. - * @param snapshot - * the snapshot identifier for the blob. + * Creates creates a new PageBlobClient object by concatenating blobName to the end of ContainerAsyncClient's URL. + * The new PageBlobClient uses the same request policy pipeline as the ContainerAsyncClient. To change the pipeline, + * create the PageBlobClient and then call its WithPipeline method passing in the desired pipeline object. Or, call + * this package's NewPageBlobAsyncClient instead of calling this object's NewPageBlobAsyncClient method. * + * @param blobName A {@code String} representing the name of the blob. + * @param snapshot the snapshot identifier for the blob. * @return A new {@link PageBlobClient} object which references the blob with the specified name in this container. */ public PageBlobClient getPageBlobClient(String blobName, String snapshot) { @@ -125,31 +113,27 @@ public PageBlobClient getPageBlobClient(String blobName, String snapshot) { } /** - * Creates creates a new AppendBlobClient object by concatenating blobName to the end of - * ContainerAsyncClient's URL. The new AppendBlobClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the AppendBlobClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's NewAppendBlobAsyncClient instead of calling this object's + * Creates creates a new AppendBlobClient object by concatenating blobName to the end of ContainerAsyncClient's URL. + * The new AppendBlobClient uses the same request policy pipeline as the ContainerAsyncClient. To change the + * pipeline, create the AppendBlobClient and then call its WithPipeline method passing in the desired pipeline + * object. Or, call this package's NewAppendBlobAsyncClient instead of calling this object's * NewAppendBlobAsyncClient method. * - * @param blobName - * A {@code String} representing the name of the blob. - * - * @return A new {@link AppendBlobClient} object which references the blob with the specified name in this container. + * @param blobName A {@code String} representing the name of the blob. + * @return A new {@link AppendBlobClient} object which references the blob with the specified name in this + * container. */ public AppendBlobClient getAppendBlobClient(String blobName) { return new AppendBlobClient(containerAsyncClient.getAppendBlobAsyncClient(blobName)); } /** - * Initializes a new BlobClient object by concatenating blobName to the end of - * ContainerAsyncClient's URL. The new BlobClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the BlobClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's getBlobAsyncClient instead of calling this object's - * getBlobAsyncClient method. - * - * @param blobName - * A {@code String} representing the name of the blob. + * Initializes a new BlobClient object by concatenating blobName to the end of ContainerAsyncClient's URL. The new + * BlobClient uses the same request policy pipeline as the ContainerAsyncClient. To change the pipeline, create the + * BlobClient and then call its WithPipeline method passing in the desired pipeline object. Or, call this package's + * getBlobAsyncClient instead of calling this object's getBlobAsyncClient method. * + * @param blobName A {@code String} representing the name of the blob. * @return A new {@link BlobClient} object which references the blob with the specified name in this container. */ public BlobClient getBlobClient(String blobName) { @@ -157,17 +141,13 @@ public BlobClient getBlobClient(String blobName) { } /** - * Initializes a new BlobClient object by concatenating blobName to the end of - * ContainerAsyncClient's URL. The new BlobClient uses the same request policy pipeline as the ContainerAsyncClient. - * To change the pipeline, create the BlobClient and then call its WithPipeline method passing in the - * desired pipeline object. Or, call this package's getBlobAsyncClient instead of calling this object's - * getBlobAsyncClient method. - * - * @param blobName - * A {@code String} representing the name of the blob. - * @param snapshot - * the snapshot identifier for the blob. + * Initializes a new BlobClient object by concatenating blobName to the end of ContainerAsyncClient's URL. The new + * BlobClient uses the same request policy pipeline as the ContainerAsyncClient. To change the pipeline, create the + * BlobClient and then call its WithPipeline method passing in the desired pipeline object. Or, call this package's + * getBlobAsyncClient instead of calling this object's getBlobAsyncClient method. * + * @param blobName A {@code String} representing the name of the blob. + * @param snapshot the snapshot identifier for the blob. * @return A new {@link BlobClient} object which references the blob with the specified name in this container. */ public BlobClient getBlobClient(String blobName, String snapshot) { @@ -177,8 +157,7 @@ public BlobClient getBlobClient(String blobName, String snapshot) { /** * Initializes a {@link StorageClient} object pointing to the storage account this container is in. * - * @return - * A {@link StorageClient} object pointing to the specified storage account + * @return A {@link StorageClient} object pointing to the specified storage account */ public StorageClient getStorageClient() { return new StorageClient(containerAsyncClient.getStorageAsyncClient()); @@ -186,6 +165,7 @@ public StorageClient getStorageClient() { /** * Gets the URL of the container represented by this client. + * * @return the URL. */ public URL getContainerUrl() { @@ -204,10 +184,8 @@ public Response exists() { /** * Gets if the container this client represents exists in the cloud. * - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * @return - * true if the container exists, false if it doesn't + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return true if the container exists, false if it doesn't */ public Response exists(Duration timeout) { Mono> response = containerAsyncClient.exists(); @@ -219,6 +197,7 @@ public Response exists(Duration timeout) { * Creates a new container within a storage account. If a container with the same name already exists, the operation * fails. For more information, see the * Azure Docs. + * * @return A response containing status code and HTTP headers */ public VoidResponse create() { @@ -230,13 +209,10 @@ public VoidResponse create() { * fails. For more information, see the * Azure Docs. * - * @param metadata - * {@link Metadata} - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param metadata {@link Metadata} + * @param accessType Specifies how the data in this container is available to the public. See the + * x-ms-blob-public-access header in the Azure Docs for more information. Pass null for no public access. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers */ public VoidResponse create(Metadata metadata, PublicAccessType accessType, Duration timeout) { @@ -246,9 +222,10 @@ public VoidResponse create(Metadata metadata, PublicAccessType accessType, Durat } /** - * Marks the specified container for deletion. The container and any blobs contained within it are later - * deleted during garbage collection. For more information, see the + * Marks the specified container for deletion. The container and any blobs contained within it are later deleted + * during garbage collection. For more information, see the * Azure Docs. + * * @return A response containing status code and HTTP headers */ public VoidResponse delete() { @@ -256,14 +233,12 @@ public VoidResponse delete() { } /** - * Marks the specified container for deletion. The container and any blobs contained within it are later - * deleted during garbage collection. For more information, see the + * Marks the specified container for deletion. The container and any blobs contained within it are later deleted + * during garbage collection. For more information, see the * Azure Docs. * - * @param accessConditions - * {@link ContainerAccessConditions} - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param accessConditions {@link ContainerAccessConditions} + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers */ public VoidResponse delete(ContainerAccessConditions accessConditions, Duration timeout) { @@ -276,8 +251,7 @@ public VoidResponse delete(ContainerAccessConditions accessConditions, Duration * Returns the container's metadata and system properties. For more information, see the * Azure Docs. * - * @return - * The container properties. + * @return The container properties. */ public Response getProperties() { return this.getProperties(null, null); @@ -287,17 +261,13 @@ public Response getProperties() { * Returns the container's metadata and system properties. For more information, see the * Azure Docs. * - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The container properties. + * @param leaseAccessConditions By setting lease access conditions, requests will fail if the provided lease does + * not match the active lease on the blob. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The container properties. */ public Response getProperties(LeaseAccessConditions leaseAccessConditions, - Duration timeout) { + Duration timeout) { Mono> response = containerAsyncClient.getProperties(leaseAccessConditions); return Utility.blockWithOptionalTimeout(response, timeout); @@ -307,8 +277,7 @@ public Response getProperties(LeaseAccessConditions leaseAc * Sets the container's metadata. For more information, see the * Azure Docs. * - * @param metadata - * {@link Metadata} + * @param metadata {@link Metadata} * @return A response containing status code and HTTP headers */ public VoidResponse setMetadata(Metadata metadata) { @@ -319,16 +288,13 @@ public VoidResponse setMetadata(Metadata metadata) { * Sets the container's metadata. For more information, see the * Azure Docs. * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link ContainerAccessConditions} - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param metadata {@link Metadata} + * @param accessConditions {@link ContainerAccessConditions} + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers */ public VoidResponse setMetadata(Metadata metadata, - ContainerAccessConditions accessConditions, Duration timeout) { + ContainerAccessConditions accessConditions, Duration timeout) { Mono response = containerAsyncClient.setMetadata(metadata, accessConditions); return Utility.blockWithOptionalTimeout(response, timeout); @@ -339,8 +305,7 @@ public VoidResponse setMetadata(Metadata metadata, * For more information, see the * Azure Docs. * - * @return - * The container access policy. + * @return The container access policy. */ public Response getAccessPolicy() { return this.getAccessPolicy(null, null); @@ -351,14 +316,10 @@ public Response getAccessPolicy() { * For more information, see the * Azure Docs. * - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The container access policy. + * @param leaseAccessConditions By setting lease access conditions, requests will fail if the provided lease does + * not match the active lease on the blob. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The container access policy. */ public Response getAccessPolicy(LeaseAccessConditions leaseAccessConditions, Duration timeout) { @@ -373,17 +334,16 @@ public Response getAccessPolicy(LeaseAccessConditions l * ensure the time formatting is compatible with the service. For more information, see the * Azure Docs. * - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param identifiers - * A list of {@link SignedIdentifier} objects that specify the permissions for the container. Please see - * here - * for more information. Passing null will clear all access policies. + * @param accessType Specifies how the data in this container is available to the public. See the + * x-ms-blob-public-access header in the Azure Docs for more information. Pass null for no public access. + * @param identifiers A list of {@link SignedIdentifier} objects that specify the permissions for the container. + * Please see + * here + * for more information. Passing null will clear all access policies. * @return A response containing status code and HTTP headers */ public VoidResponse setAccessPolicy(PublicAccessType accessType, - List identifiers) { + List identifiers) { return this.setAccessPolicy(accessType, identifiers, null, null); } @@ -393,31 +353,27 @@ public VoidResponse setAccessPolicy(PublicAccessType accessType, * ensure the time formatting is compatible with the service. For more information, see the * Azure Docs. * - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param identifiers - * A list of {@link SignedIdentifier} objects that specify the permissions for the container. Please see - * here - * for more information. Passing null will clear all access policies. - * @param accessConditions - * {@link ContainerAccessConditions} - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param accessType Specifies how the data in this container is available to the public. See the + * x-ms-blob-public-access header in the Azure Docs for more information. Pass null for no public access. + * @param identifiers A list of {@link SignedIdentifier} objects that specify the permissions for the container. + * Please see + * here + * for more information. Passing null will clear all access policies. + * @param accessConditions {@link ContainerAccessConditions} + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers */ public VoidResponse setAccessPolicy(PublicAccessType accessType, - List identifiers, ContainerAccessConditions accessConditions, - Duration timeout) { + List identifiers, ContainerAccessConditions accessConditions, + Duration timeout) { Mono response = containerAsyncClient.setAccessPolicy(accessType, identifiers, accessConditions); return Utility.blockWithOptionalTimeout(response, timeout); } /** - * Returns a lazy loaded list of blobs in this container, with folder structures flattened. - * The returned {@link Iterable} can be iterated through while new items are automatically - * retrieved as needed. + * Returns a lazy loaded list of blobs in this container, with folder structures flattened. The returned {@link + * Iterable} can be iterated through while new items are automatically retrieved as needed. * *

* Blob names are returned in lexicographic order. @@ -426,17 +382,15 @@ public VoidResponse setAccessPolicy(PublicAccessType accessType, * For more information, see the * Azure Docs. * - * @return - * The listed blobs, flattened. + * @return The listed blobs, flattened. */ public Iterable listBlobsFlat() { return this.listBlobsFlat(new ListBlobsOptions(), null); } /** - * Returns a lazy loaded list of blobs in this container, with folder structures flattened. - * The returned {@link Iterable} can be iterated through while new items are automatically - * retrieved as needed. + * Returns a lazy loaded list of blobs in this container, with folder structures flattened. The returned {@link + * Iterable} can be iterated through while new items are automatically retrieved as needed. * *

* Blob names are returned in lexicographic order. @@ -445,13 +399,9 @@ public Iterable listBlobsFlat() { * For more information, see the * Azure Docs. * - * @param options - * {@link ListBlobsOptions} - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The listed blobs, flattened. + * @param options {@link ListBlobsOptions} + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The listed blobs, flattened. */ public Iterable listBlobsFlat(ListBlobsOptions options, Duration timeout) { Flux response = containerAsyncClient.listBlobsFlat(options); @@ -460,74 +410,64 @@ public Iterable listBlobsFlat(ListBlobsOptions options, Duration timeo } /** - * Returns a reactive Publisher emitting all the blobs and directories (prefixes) under - * the given directory (prefix). Directories will have {@link BlobItem#isPrefix()} set to - * true. + * Returns a reactive Publisher emitting all the blobs and directories (prefixes) under the given directory + * (prefix). Directories will have {@link BlobItem#isPrefix()} set to true. * *

* Blob names are returned in lexicographic order. For more information, see the * Azure Docs. * *

- * E.g. listing a container containing a 'foo' folder, which contains blobs 'foo1' and 'foo2', and a blob - * on the root level 'bar', will return the following results when prefix=null: + * E.g. listing a container containing a 'foo' folder, which contains blobs 'foo1' and 'foo2', and a blob on the + * root level 'bar', will return the following results when prefix=null: * *

    - *
  • foo/ (isPrefix = true) - *
  • bar (isPrefix = false) + *
  • foo/ (isPrefix = true) + *
  • bar (isPrefix = false) *
*

* will return the following results when prefix="foo/": * *

    - *
  • foo/foo1 (isPrefix = false) - *
  • foo/foo2 (isPrefix = false) + *
  • foo/foo1 (isPrefix = false) + *
  • foo/foo2 (isPrefix = false) *
* - * @param directory - * The directory to list blobs underneath - * - * @return - * A reactive response emitting the prefixes and blobs. + * @param directory The directory to list blobs underneath + * @return A reactive response emitting the prefixes and blobs. */ public Iterable listBlobsHierarchy(String directory) { return this.listBlobsHierarchy("/", new ListBlobsOptions().prefix(directory), null); } /** - * Returns a reactive Publisher emitting all the blobs and prefixes (directories) under - * the given prefix (directory). Directories will have {@link BlobItem#isPrefix()} set to - * true. + * Returns a reactive Publisher emitting all the blobs and prefixes (directories) under the given prefix + * (directory). Directories will have {@link BlobItem#isPrefix()} set to true. * *

* Blob names are returned in lexicographic order. For more information, see the * Azure Docs. * *

- * E.g. listing a container containing a 'foo' folder, which contains blobs 'foo1' and 'foo2', and a blob - * on the root level 'bar', will return the following results when prefix=null: + * E.g. listing a container containing a 'foo' folder, which contains blobs 'foo1' and 'foo2', and a blob on the + * root level 'bar', will return the following results when prefix=null: * *

    - *
  • foo/ (isPrefix = true) - *
  • bar (isPrefix = false) + *
  • foo/ (isPrefix = true) + *
  • bar (isPrefix = false) *
*

* will return the following results when prefix="foo/": * *

    - *
  • foo/foo1 (isPrefix = false) - *
  • foo/foo2 (isPrefix = false) + *
  • foo/foo1 (isPrefix = false) + *
  • foo/foo2 (isPrefix = false) *
* - * @param delimiter - * The delimiter for blob hierarchy, "/" for hierarchy based on directories - * @param options - * {@link ListBlobsOptions} - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * A reactive response emitting the prefixes and blobs. + * @param delimiter The delimiter for blob hierarchy, "/" for hierarchy based on directories + * @param options {@link ListBlobsOptions} + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return A reactive response emitting the prefixes and blobs. */ public Iterable listBlobsHierarchy(String delimiter, ListBlobsOptions options, Duration timeout) { Flux response = containerAsyncClient.listBlobsHierarchy(delimiter, options); @@ -539,14 +479,10 @@ public Iterable listBlobsHierarchy(String delimiter, ListBlobsOptions * Acquires a lease on the blob for write and delete operations. The lease duration must be between 15 to 60 * seconds, or infinite (-1). * - * @param proposedId - * A {@code String} in any valid GUID format. May be null. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that - * never expires. A non-infinite lease can be between 15 and 60 seconds. - * - * @return - * The lease ID. + * @param proposedId A {@code String} in any valid GUID format. May be null. + * @param duration The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. A + * non-infinite lease can be between 15 and 60 seconds. + * @return The lease ID. */ public Response acquireLease(String proposedId, int duration) { return this.acquireLease(proposedId, duration, null, null); @@ -556,23 +492,17 @@ public Response acquireLease(String proposedId, int duration) { * Acquires a lease on the blob for write and delete operations. The lease duration must be between 15 to 60 * seconds, or infinite (-1). * - * @param proposedID - * A {@code String} in any valid GUID format. May be null. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that - * never expires. A non-infinite lease can be between 15 and 60 seconds. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The lease ID. + * @param proposedID A {@code String} in any valid GUID format. May be null. + * @param duration The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. A + * non-infinite lease can be between 15 and 60 seconds. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The lease ID. */ public Response acquireLease(String proposedID, int duration, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { + ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { Mono> response = containerAsyncClient .acquireLease(proposedID, duration, modifiedAccessConditions); @@ -582,11 +512,8 @@ public Response acquireLease(String proposedID, int duration, /** * Renews the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * - * @return - * The renewed lease ID. + * @param leaseID The leaseId of the active lease on the blob. + * @return The renewed lease ID. */ public Response renewLease(String leaseID) { return this.renewLease(leaseID, null, null); @@ -595,20 +522,15 @@ public Response renewLease(String leaseID) { /** * Renews the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The renewed lease ID. + * @param leaseID The leaseId of the active lease on the blob. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The renewed lease ID. */ public Response renewLease(String leaseID, ModifiedAccessConditions modifiedAccessConditions, - Duration timeout) { + Duration timeout) { Mono> response = containerAsyncClient .renewLease(leaseID, modifiedAccessConditions); @@ -618,8 +540,7 @@ public Response renewLease(String leaseID, ModifiedAccessConditions modi /** * Releases the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. + * @param leaseID The leaseId of the active lease on the blob. * @return A response containing status code and HTTP headers */ public VoidResponse releaseLease(String leaseID) { @@ -629,18 +550,15 @@ public VoidResponse releaseLease(String leaseID) { /** * Releases the blob's previously-acquired lease. * - * @param leaseID - * The leaseId of the active lease on the blob. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param leaseID The leaseId of the active lease on the blob. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return A response containing status code and HTTP headers. */ public VoidResponse releaseLease(String leaseID, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { + ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { Mono response = containerAsyncClient .releaseLease(leaseID, modifiedAccessConditions); @@ -651,8 +569,7 @@ public VoidResponse releaseLease(String leaseID, * BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1) constant * to break a fixed-duration lease when it expires or an infinite lease immediately. * - * @return - * The remaining time in the broken lease. + * @return The remaining time in the broken lease. */ public Response breakLease() { return this.breakLease(null, null, null); @@ -662,24 +579,19 @@ public Response breakLease() { * BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1) constant * to break a fixed-duration lease when it expires or an infinite lease immediately. * - * @param breakPeriodInSeconds - * An optional {@code Integer} representing the proposed duration of seconds that the lease should continue - * before it is broken, between 0 and 60 seconds. This break period is only used if it is shorter than the - * time remaining on the lease. If longer, the time remaining on the lease is used. A new lease will not be - * available before the break period has expired, but the lease may be held for longer than the break - * period. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The remaining time in the broken lease. + * @param breakPeriodInSeconds An optional {@code Integer} representing the proposed duration of seconds that the + * lease should continue before it is broken, between 0 and 60 seconds. This break period is only used if it is + * shorter than the time remaining on the lease. If longer, the time remaining on the lease is used. A new lease + * will not be available before the break period has expired, but the lease may be held for longer than the break + * period. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The remaining time in the broken lease. */ public Response breakLease(Integer breakPeriodInSeconds, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { + ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { Mono> response = containerAsyncClient .breakLease(breakPeriodInSeconds, modifiedAccessConditions); @@ -689,36 +601,28 @@ public Response breakLease(Integer breakPeriodInSeconds, /** * ChangeLease changes the blob's lease ID. * - * @param leaseId - * The leaseId of the active lease on the blob. - * @param proposedID - * A {@code String} in any valid GUID format. - * - * @return - * The new lease ID. + * @param leaseId The leaseId of the active lease on the blob. + * @param proposedID A {@code String} in any valid GUID format. + * @return The new lease ID. */ public Response changeLease(String leaseId, String proposedID) { return this.changeLease(leaseId, proposedID, null, null); } /** - * ChangeLease changes the blob's lease ID. For more information, see the Azure Docs. - * - * @param leaseId - * The leaseId of the active lease on the blob. - * @param proposedID - * A {@code String} in any valid GUID format. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * ChangeLease changes the blob's lease ID. For more information, see the Azure + * Docs. * + * @param leaseId The leaseId of the active lease on the blob. + * @param proposedID A {@code String} in any valid GUID format. + * @param modifiedAccessConditions Standard HTTP Access conditions related to the modification of data. ETag and + * LastModifiedTime are used to construct conditions related to when the blob was changed relative to the given + * request. The request will fail if the specified condition is not satisfied. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. * @return The new lease ID. */ public Response changeLease(String leaseId, String proposedID, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { + ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { Mono> response = containerAsyncClient .changeLease(leaseId, proposedID, modifiedAccessConditions); @@ -729,11 +633,8 @@ public Response changeLease(String leaseId, String proposedID, * Returns the sku name and account kind for the account. For more information, please see the * Azure Docs. * - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The account info. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The account info. */ public Response getAccountInfo(Duration timeout) { Mono> response = containerAsyncClient.getAccountInfo(); @@ -744,20 +645,14 @@ public Response getAccountInfo(Duration timeout) { /** * Generates a user delegation SAS token with the specified parameters * - * @param userDelegationKey - * The {@code UserDelegationKey} user delegation key for the SAS - * @param accountName - * The {@code String} account name for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * - * @return - * A string that represents the SAS token + * @param userDelegationKey The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName The {@code String} account name for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @return A string that represents the SAS token */ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, - ContainerSASPermission permissions, OffsetDateTime expiryTime) { + ContainerSASPermission permissions, OffsetDateTime expiryTime) { return this.containerAsyncClient.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime); } @@ -765,29 +660,19 @@ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, Str /** * Generates a user delegation SAS token with the specified parameters * - * @param userDelegationKey - * The {@code UserDelegationKey} user delegation key for the SAS - * @param accountName - * The {@code String} account name for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * - * @return - * A string that represents the SAS token + * @param userDelegationKey The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName The {@code String} account name for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @return A string that represents the SAS token */ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, - ContainerSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, - SASProtocol sasProtocol, IPRange ipRange) { + ContainerSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange) { return this.containerAsyncClient.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, startTime, version, sasProtocol, ipRange); } @@ -795,40 +680,25 @@ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, Str /** * Generates a user delegation SAS token with the specified parameters * - * @param userDelegationKey - * The {@code UserDelegationKey} user delegation key for the SAS - * @param accountName - * The {@code String} account name for the SAS - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * @param cacheControl - * An optional {@code String} cache-control header for the SAS. - * @param contentDisposition - * An optional {@code String} content-disposition header for the SAS. - * @param contentEncoding - * An optional {@code String} content-encoding header for the SAS. - * @param contentLanguage - * An optional {@code String} content-language header for the SAS. - * @param contentType - * An optional {@code String} content-type header for the SAS. - * - * @return - * A string that represents the SAS token + * @param userDelegationKey The {@code UserDelegationKey} user delegation key for the SAS + * @param accountName The {@code String} account name for the SAS + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @param cacheControl An optional {@code String} cache-control header for the SAS. + * @param contentDisposition An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage An optional {@code String} content-language header for the SAS. + * @param contentType An optional {@code String} content-type header for the SAS. + * @return A string that represents the SAS token */ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, String accountName, - ContainerSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, - SASProtocol sasProtocol, IPRange ipRange, String cacheControl, String contentDisposition, - String contentEncoding, String contentLanguage, String contentType) { + ContainerSASPermission permissions, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, + SASProtocol sasProtocol, IPRange ipRange, String cacheControl, String contentDisposition, + String contentEncoding, String contentLanguage, String contentType) { return this.containerAsyncClient.generateUserDelegationSAS(userDelegationKey, accountName, permissions, expiryTime, startTime, version, sasProtocol, ipRange, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); @@ -837,13 +707,9 @@ public String generateUserDelegationSAS(UserDelegationKey userDelegationKey, Str /** * Generates a SAS token with the specified parameters * - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * - * @return - * A string that represents the SAS token + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @return A string that represents the SAS token */ public String generateSAS(ContainerSASPermission permissions, OffsetDateTime expiryTime) { return this.containerAsyncClient.generateSAS(permissions, expiryTime); @@ -852,11 +718,8 @@ public String generateSAS(ContainerSASPermission permissions, OffsetDateTime exp /** * Generates a SAS token with the specified parameters * - * @param identifier - * The {@code String} name of the access policy on the container this SAS references if any - * - * @return - * A string that represents the SAS token + * @param identifier The {@code String} name of the access policy on the container this SAS references if any + * @return A string that represents the SAS token */ public String generateSAS(String identifier) { return this.containerAsyncClient.generateSAS(identifier); @@ -865,26 +728,17 @@ public String generateSAS(String identifier) { /** * Generates a SAS token with the specified parameters * - * @param identifier - * The {@code String} name of the access policy on the container this SAS references if any - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * - * @return - * A string that represents the SAS token + * @param identifier The {@code String} name of the access policy on the container this SAS references if any + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @return A string that represents the SAS token */ public String generateSAS(String identifier, ContainerSASPermission permissions, OffsetDateTime expiryTime, - OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange) { + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange) { return this.containerAsyncClient.generateSAS(identifier, permissions, expiryTime, startTime, version, sasProtocol, ipRange); } @@ -892,37 +746,23 @@ public String generateSAS(String identifier, ContainerSASPermission permissions, /** * Generates a SAS token with the specified parameters * - * @param identifier - * The {@code String} name of the access policy on the container this SAS references if any - * @param permissions - * The {@code ContainerSASPermissions} permission for the SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the SAS - * @param startTime - * An optional {@code OffsetDateTime} start time for the SAS - * @param version - * An optional {@code String} version for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * @param cacheControl - * An optional {@code String} cache-control header for the SAS. - * @param contentDisposition - * An optional {@code String} content-disposition header for the SAS. - * @param contentEncoding - * An optional {@code String} content-encoding header for the SAS. - * @param contentLanguage - * An optional {@code String} content-language header for the SAS. - * @param contentType - * An optional {@code String} content-type header for the SAS. - * - * @return - * A string that represents the SAS token + * @param identifier The {@code String} name of the access policy on the container this SAS references if any + * @param permissions The {@code ContainerSASPermissions} permission for the SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the SAS + * @param startTime An optional {@code OffsetDateTime} start time for the SAS + * @param version An optional {@code String} version for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @param cacheControl An optional {@code String} cache-control header for the SAS. + * @param contentDisposition An optional {@code String} content-disposition header for the SAS. + * @param contentEncoding An optional {@code String} content-encoding header for the SAS. + * @param contentLanguage An optional {@code String} content-language header for the SAS. + * @param contentType An optional {@code String} content-type header for the SAS. + * @return A string that represents the SAS token */ public String generateSAS(String identifier, ContainerSASPermission permissions, OffsetDateTime expiryTime, - OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange, String cacheControl, - String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { + OffsetDateTime startTime, String version, SASProtocol sasProtocol, IPRange ipRange, String cacheControl, + String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { return this.containerAsyncClient.generateSAS(identifier, permissions, expiryTime, startTime, version, sasProtocol, ipRange, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType); } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerRawClient.java deleted file mode 100644 index 19d10f5444ee5..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerRawClient.java +++ /dev/null @@ -1,652 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.http.rest.Response; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.ContainerAccessConditions; -import com.azure.storage.blob.models.ContainerAccessPolicies; -import com.azure.storage.blob.models.ContainersAcquireLeaseResponse; -import com.azure.storage.blob.models.ContainersBreakLeaseResponse; -import com.azure.storage.blob.models.ContainersChangeLeaseResponse; -import com.azure.storage.blob.models.ContainersCreateResponse; -import com.azure.storage.blob.models.ContainersDeleteResponse; -import com.azure.storage.blob.models.ContainersGetAccountInfoResponse; -import com.azure.storage.blob.models.ContainersGetPropertiesResponse; -import com.azure.storage.blob.models.ContainersListBlobFlatSegmentResponse; -import com.azure.storage.blob.models.ContainersListBlobHierarchySegmentResponse; -import com.azure.storage.blob.models.ContainersReleaseLeaseResponse; -import com.azure.storage.blob.models.ContainersRenewLeaseResponse; -import com.azure.storage.blob.models.ContainersSetAccessPolicyResponse; -import com.azure.storage.blob.models.ContainersSetMetadataResponse; -import com.azure.storage.blob.models.LeaseAccessConditions; -import com.azure.storage.blob.models.ListBlobsOptions; -import com.azure.storage.blob.models.Metadata; -import com.azure.storage.blob.models.ModifiedAccessConditions; -import com.azure.storage.blob.models.PublicAccessType; -import com.azure.storage.blob.models.SignedIdentifier; -import reactor.core.publisher.Mono; - -import java.time.Duration; -import java.util.List; - -/** - * Represents a URL to a container. It may be obtained by direct construction or via the create method on a - * {@link StorageAsyncRawClient} object. This class does not hold any state about a particular blob but is instead a convenient way - * of sending off appropriate requests to the resource on the service. It may also be used to construct URLs to blobs. - * Please refer to the - * Azure Docs - * for more information on containers. - */ -final class ContainerRawClient { - - private ContainerAsyncRawClient containerAsyncRawClient; - - public static final String ROOT_CONTAINER_NAME = "$root"; - - public static final String STATIC_WEBSITE_CONTAINER_NAME = "$web"; - - public static final String LOG_CONTAINER_NAME = "$logs"; - - - /** - * Creates a {@code ContainerAsyncClient} object pointing to the account specified by the URL and using the provided - * pipeline to make HTTP requests. - */ - ContainerRawClient(AzureBlobStorageImpl azureBlobStorage) { - this.containerAsyncRawClient = new ContainerAsyncRawClient(azureBlobStorage); - } - - /** - * Creates a new container within a storage account. If a container with the same name already exists, the operation - * fails. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersCreateResponse create() { - return this.create(null, null, null); - } - - /** - * Creates a new container within a storage account. If a container with the same name already exists, the operation - * fails. For more information, see the - * Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersCreateResponse create(Metadata metadata, PublicAccessType accessType, Duration timeout) { - Mono response = containerAsyncRawClient.create(metadata, accessType); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Marks the specified container for deletion. The container and any blobs contained within it are later - * deleted during garbage collection. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.delete")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersDeleteResponse delete() { - return this.delete(null, null); - } - - /** - * Marks the specified container for deletion. The container and any blobs contained within it are later - * deleted during garbage collection. For more information, see the - * Azure Docs. - * - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.delete")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersDeleteResponse delete(ContainerAccessConditions accessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.delete(accessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Returns the container's metadata and system properties. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersGetPropertiesResponse getProperties() { - return this.getProperties(null, null); - } - - /** - * Returns the container's metadata and system properties. For more information, see the - * Azure Docs. - * - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersGetPropertiesResponse getProperties(LeaseAccessConditions leaseAccessConditions, - Duration timeout) { - Mono response = containerAsyncRawClient.getProperties(leaseAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Sets the container's metadata. For more information, see the - * Azure Docs. - * - * @param metadata - * {@link Metadata} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.setMetadata")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersSetMetadataResponse setMetadata(Metadata metadata) { - return this.setMetadata(metadata, null, null); - } - - /** - * Sets the container's metadata. For more information, see the - * Azure Docs. - * - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_basic "Sample code for ContainerAsyncClient.setMetadata")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersSetMetadataResponse setMetadata(Metadata metadata, - ContainerAccessConditions accessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.setMetadata(metadata, accessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Returns the container's permissions. The permissions indicate whether container's blobs may be accessed publicly. - * For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.getAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Response getAccessPolicy() { - return this.getAccessPolicy(null, null); - } - - /** - * Returns the container's permissions. The permissions indicate whether container's blobs may be accessed publicly. - * For more information, see the - * Azure Docs. - * - * @param leaseAccessConditions - * By setting lease access conditions, requests will fail if the provided lease does not match the active - * lease on the blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.getAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Response getAccessPolicy(LeaseAccessConditions leaseAccessConditions, - Duration timeout) { - Mono> response = containerAsyncRawClient.getAccessPolicy(leaseAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Sets the container's permissions. The permissions indicate whether blobs in a container may be accessed publicly. - * Note that, for each signed identifier, we will truncate the start and expiry times to the nearest second to - * ensure the time formatting is compatible with the service. For more information, see the - * Azure Docs. - * - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param identifiers - * A list of {@link SignedIdentifier} objects that specify the permissions for the container. Please see - * here - * for more information. Passing null will clear all access policies. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.setAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersSetAccessPolicyResponse setAccessPolicy(PublicAccessType accessType, - List identifiers) { - return this.setAccessPolicy(accessType, identifiers, null, null); - } - - /** - * Sets the container's permissions. The permissions indicate whether blobs in a container may be accessed publicly. - * Note that, for each signed identifier, we will truncate the start and expiry times to the nearest second to - * ensure the time formatting is compatible with the service. For more information, see the - * Azure Docs. - * - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. - * @param identifiers - * A list of {@link SignedIdentifier} objects that specify the permissions for the container. Please see - * here - * for more information. Passing null will clear all access policies. - * @param accessConditions - * {@link ContainerAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_policy "Sample code for ContainerAsyncClient.setAccessPolicy")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersSetAccessPolicyResponse setAccessPolicy(PublicAccessType accessType, - List identifiers, ContainerAccessConditions accessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.setAccessPolicy(accessType, identifiers, accessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Acquires a lease on the container for delete operations. The lease duration must be between 15 to - * 60 seconds, or infinite (-1). For more information, see the - * Azure Docs. - * - * @apiNote - * ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - * - * @param proposedId - * A {@code String} in any valid GUID format. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. - * A non-infinite lease can be between 15 and 60 seconds. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersAcquireLeaseResponse acquireLease(String proposedId, int duration) { - return this.acquireLease(proposedId, duration, null, null); - } - - /** - * Acquires a lease on the container for delete operations. The lease duration must be between 15 to - * 60 seconds, or infinite (-1). For more information, see the - * Azure Docs. - * - * @param proposedID - * A {@code String} in any valid GUID format. - * @param duration - * The duration of the lease, in seconds, or negative one (-1) for a lease that never expires. - * A non-infinite lease can be between 15 and 60 seconds. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.acquireLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersAcquireLeaseResponse acquireLease(String proposedID, int duration, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.acquireLease(proposedID, duration, modifiedAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Renews the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.renewLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersRenewLeaseResponse renewLease(String leaseID) { - return this.renewLease(leaseID, null, null); - } - - /** - * Renews the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.renewLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersRenewLeaseResponse renewLease(String leaseID, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.renewLease(leaseID, modifiedAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Releases the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.releaseLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersReleaseLeaseResponse releaseLease(String leaseID) { - return this.releaseLease(leaseID, null, null); - } - - /** - * Releases the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.releaseLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersReleaseLeaseResponse releaseLease(String leaseID, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.releaseLease(leaseID, modifiedAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Breaks the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @apiNote - * ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.breakLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/New-Storage-SDK-V10-Preview/src/test/java/com/microsoft/azure/storage/Samples.java) - * - * @return Emits the successful response. - */ - public ContainersBreakLeaseResponse breakLease() { - return this.breakLease(null, null, null); - } - - /** - * Breaks the container's previously-acquired lease. For more information, see the - * Azure Docs. - * - * @param breakPeriodInSeconds - * An optional {@code Integer} representing the proposed duration of seconds that the lease should continue - * before it is broken, between 0 and 60 seconds. This break period is only used if it is shorter than the time - * remaining on the lease. If longer, the time remaining on the lease is used. A new lease will not be - * available before the break period has expired, but the lease may be held for longer than the break period. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.breakLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersBreakLeaseResponse breakLease(Integer breakPeriodInSeconds, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.breakLease(breakPeriodInSeconds, modifiedAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Changes the container's leaseAccessConditions. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param proposedID - * A {@code String} in any valid GUID format. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.changeLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersChangeLeaseResponse changeLease(String leaseID, String proposedID) { - return this.changeLease(leaseID, proposedID, null, null); - } - - /** - * Changes the container's leaseAccessConditions. For more information, see the - * Azure Docs. - * - * @param leaseID - * The leaseId of the active lease on the container. - * @param proposedID - * A {@code String} in any valid GUID format. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=container_lease "Sample code for ContainerAsyncClient.changeLease")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersChangeLeaseResponse changeLease(String leaseID, String proposedID, - ModifiedAccessConditions modifiedAccessConditions, Duration timeout) { - Mono response = containerAsyncRawClient.changeLease(leaseID, proposedID, modifiedAccessConditions); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Returns a single segment of blobs starting from the specified Marker. Use an empty - * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. - * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned - * Marker) to get the next segment. For more information, see the - * Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListBlobsFlatSegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param options - * {@link ListBlobsOptions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat "Sample code for ContainerAsyncClient.listBlobsFlatSegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat_helper "helper code for ContainerAsyncClient.listBlobsFlatSegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersListBlobFlatSegmentResponse listBlobsFlatSegment(String marker, ListBlobsOptions options) { - return this.listBlobsFlatSegment(marker, options, null); - } - - /** - * Returns a single segment of blobs starting from the specified Marker. Use an empty - * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. - * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned - * Marker) to get the next segment. For more information, see the - * Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListBlobsFlatSegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param options - * {@link ListBlobsOptions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat "Sample code for ContainerAsyncClient.listBlobsFlatSegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_flat_helper "helper code for ContainerAsyncClient.listBlobsFlatSegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersListBlobFlatSegmentResponse listBlobsFlatSegment(String marker, ListBlobsOptions options, - Duration timeout) { - Mono response = containerAsyncRawClient.listBlobsFlatSegment(marker, options); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Returns a single segment of blobs and blob prefixes starting from the specified Marker. Use an empty - * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. - * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned - * Marker) to get the next segment. For more information, see the - * Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListBlobsHierarchySegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param delimiter - * The operation returns a BlobPrefix element in the response body that acts as a placeholder for all blobs - * whose names begin with the same substring up to the appearance of the delimiter character. The delimiter may - * be a single character or a string. - * @param options - * {@link ListBlobsOptions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy "Sample code for ContainerAsyncClient.listBlobsHierarchySegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy_helper "helper code for ContainerAsyncClient.listBlobsHierarchySegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersListBlobHierarchySegmentResponse listBlobsHierarchySegment(String marker, String delimiter, - ListBlobsOptions options) { - return this.listBlobsHierarchySegment(marker, delimiter, options, null); - } - - /** - * Returns a single segment of blobs and blob prefixes starting from the specified Marker. Use an empty - * marker to start enumeration from the beginning. Blob names are returned in lexicographic order. - * After getting a segment, process it, and then call ListBlobs again (passing the the previously-returned - * Marker) to get the next segment. For more information, see the - * Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListBlobsHierarchySegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param delimiter - * The operation returns a BlobPrefix element in the response body that acts as a placeholder for all blobs - * whose names begin with the same substring up to the appearance of the delimiter character. The delimiter may - * be a single character or a string. - * @param options - * {@link ListBlobsOptions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy "Sample code for ContainerAsyncClient.listBlobsHierarchySegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=list_blobs_hierarchy_helper "helper code for ContainerAsyncClient.listBlobsHierarchySegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersListBlobHierarchySegmentResponse listBlobsHierarchySegment(String marker, String delimiter, - ListBlobsOptions options, Duration timeout) { - Mono response = containerAsyncRawClient.listBlobsHierarchySegment(marker, delimiter, options); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for ContainerAsyncClient.getAccountInfo")] \n - * For more samples, please see the [Samples file] (https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersGetAccountInfoResponse getAccountInfo() { - return this.getAccountInfo(null); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for ContainerAsyncClient.getAccountInfo")] \n - * For more samples, please see the [Samples file] (https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ContainersGetAccountInfoResponse getAccountInfo(Duration timeout) { - Mono response = containerAsyncRawClient.getAccountInfo(); - return Utility.blockWithOptionalTimeout(response, timeout); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncClient.java index 073df29744582..72f052fd27573 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncClient.java @@ -5,6 +5,8 @@ import com.azure.core.http.rest.Response; import com.azure.core.http.rest.SimpleResponse; +import com.azure.core.implementation.http.UrlBuilder; +import com.azure.core.util.Context; import com.azure.storage.blob.implementation.AzureBlobStorageBuilder; import com.azure.storage.blob.models.BlobAccessConditions; import com.azure.storage.blob.models.BlobHTTPHeaders; @@ -17,12 +19,14 @@ import com.azure.storage.blob.models.PageRange; import com.azure.storage.blob.models.SequenceNumberActionType; import com.azure.storage.blob.models.SourceModifiedAccessConditions; -import io.netty.buffer.Unpooled; +import io.netty.buffer.ByteBuf; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.net.MalformedURLException; import java.net.URL; -import java.nio.ByteBuffer; + +import static com.azure.storage.blob.Utility.postProcessResponse; /** * Client to a page blob. It may only be instantiated through a {@link PageBlobClientBuilder}, via @@ -48,9 +52,6 @@ * object through {@link Mono#toFuture()}. */ public final class PageBlobAsyncClient extends BlobAsyncClient { - - final PageBlobAsyncRawClient pageBlobAsyncRawClient; - /** * Indicates the number of bytes in a page. */ @@ -67,7 +68,6 @@ public final class PageBlobAsyncClient extends BlobAsyncClient { */ PageBlobAsyncClient(AzureBlobStorageBuilder azureBlobStorageBuilder, String snapshot) { super(azureBlobStorageBuilder, snapshot); - this.pageBlobAsyncRawClient = new PageBlobAsyncRawClient(azureBlobStorageBuilder.build(), snapshot); } /** @@ -104,13 +104,30 @@ public Mono> create(long size) { * @param accessConditions * {@link BlobAccessConditions} * - * @return - * A reactive response containing the information of the created page blob. + * @return A reactive response containing the information of the created page blob. + * @throws IllegalArgumentException If {@code size} isn't a multiple of {@link PageBlobAsyncClient#PAGE_BYTES} + * or {@code sequenceNumber} isn't null and is less than 0. */ public Mono> create(long size, Long sequenceNumber, BlobHTTPHeaders headers, Metadata metadata, BlobAccessConditions accessConditions) { - return pageBlobAsyncRawClient - .create(size, sequenceNumber, headers, metadata, accessConditions) + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + if (size % PAGE_BYTES != 0) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("size must be a multiple of PageBlobAsyncClient.PAGE_BYTES."); + } + if (sequenceNumber != null && sequenceNumber < 0) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("SequenceNumber must be greater than or equal to 0."); + } + metadata = metadata == null ? new Metadata() : metadata; + + return postProcessResponse(this.azureBlobStorage.pageBlobs().createWithRestResponseAsync(null, + null, 0, size, null, metadata, null, null, + null, sequenceNumber, null, headers, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new PageBlobItem(rb.deserializedHeaders()))); } @@ -133,7 +150,7 @@ public Mono> create(long size, Long sequenceNumber, BlobH * @return * A reactive response containing the information of the uploaded pages. */ - public Mono> uploadPages(PageRange pageRange, Flux body) { + public Mono> uploadPages(PageRange pageRange, Flux body) { return this.uploadPages(pageRange, body, null); } @@ -155,13 +172,25 @@ public Mono> uploadPages(PageRange pageRange, Flux> uploadPages(PageRange pageRange, Flux body, + public Mono> uploadPages(PageRange pageRange, Flux body, PageBlobAccessConditions pageBlobAccessConditions) { - return pageBlobAsyncRawClient - .uploadPages(pageRange, body.map(Unpooled::wrappedBuffer), pageBlobAccessConditions) + pageBlobAccessConditions = pageBlobAccessConditions == null ? new PageBlobAccessConditions() : pageBlobAccessConditions; + + if (pageRange == null) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("pageRange cannot be null."); + } + String pageRangeStr = pageRangeToString(pageRange); + + return postProcessResponse(this.azureBlobStorage.pageBlobs().uploadPagesWithRestResponseAsync(null, + null, body, pageRange.end() - pageRange.start() + 1, null, + null, pageRangeStr, null, null, null, null, + pageBlobAccessConditions.leaseAccessConditions(), pageBlobAccessConditions.sequenceNumberAccessConditions(), + pageBlobAccessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new PageBlobItem(rb.deserializedHeaders()))); } @@ -218,15 +247,33 @@ public Mono> uploadPagesFromURL(PageRange range, URL sour * @param sourceAccessConditions * {@link SourceModifiedAccessConditions} * - * @return - * A reactive response containing the information of the uploaded pages. + * @return A reactive response containing the information of the uploaded pages. + * @throws IllegalArgumentException If {@code range} is {@code null} */ public Mono> uploadPagesFromURL(PageRange range, URL sourceURL, Long sourceOffset, byte[] sourceContentMD5, PageBlobAccessConditions destAccessConditions, SourceModifiedAccessConditions sourceAccessConditions) { + if (range == null) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("range cannot be null."); + } + + String rangeString = pageRangeToString(range); - return pageBlobAsyncRawClient - .uploadPagesFromURL(range, sourceURL, sourceOffset, sourceContentMD5, destAccessConditions, sourceAccessConditions) + if (sourceOffset == null) { + sourceOffset = 0L; + } + + String sourceRangeString = pageRangeToString(new PageRange().start(sourceOffset).end(sourceOffset + (range.end() - range.start()))); + + destAccessConditions = destAccessConditions == null ? new PageBlobAccessConditions() : destAccessConditions; + + return postProcessResponse(this.azureBlobStorage.pageBlobs().uploadPagesFromURLWithRestResponseAsync( + null, null, sourceURL, sourceRangeString, 0, rangeString, sourceContentMD5, + null, null, destAccessConditions.leaseAccessConditions(), + destAccessConditions.sequenceNumberAccessConditions(), destAccessConditions.modifiedAccessConditions(), + sourceAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new PageBlobItem(rb.deserializedHeaders()))); } @@ -259,13 +306,23 @@ public Mono> clearPages(PageRange pageRange) { * @param pageBlobAccessConditions * {@link PageBlobAccessConditions} * - * @return - * A reactive response containing the information of the cleared pages. + * @return A reactive response containing the information of the cleared pages. + * @throws IllegalArgumentException If {@code pageRange} is {@code null} */ public Mono> clearPages(PageRange pageRange, PageBlobAccessConditions pageBlobAccessConditions) { - return pageBlobAsyncRawClient - .clearPages(pageRange, pageBlobAccessConditions) + pageBlobAccessConditions = pageBlobAccessConditions == null ? new PageBlobAccessConditions() : pageBlobAccessConditions; + if (pageRange == null) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("pageRange cannot be null."); + } + String pageRangeStr = pageRangeToString(pageRange); + + return postProcessResponse(this.azureBlobStorage.pageBlobs().clearPagesWithRestResponseAsync(null, + null, 0, null, pageRangeStr, null, + pageBlobAccessConditions.leaseAccessConditions(), pageBlobAccessConditions.sequenceNumberAccessConditions(), + pageBlobAccessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new PageBlobItem(rb.deserializedHeaders()))); } @@ -295,10 +352,14 @@ public Flux getPageRanges(BlobRange blobRange) { * @return * A reactive response emitting all the page ranges. */ - public Flux getPageRanges(BlobRange blobRange, - BlobAccessConditions accessConditions) { - return pageBlobAsyncRawClient - .getPageRanges(blobRange, accessConditions) + public Flux getPageRanges(BlobRange blobRange, BlobAccessConditions accessConditions) { + blobRange = blobRange == null ? new BlobRange(0) : blobRange; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.pageBlobs().getPageRangesWithRestResponseAsync( + null, null, snapshot, null, null, blobRange.toHeaderValue(), + null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), + Context.NONE)) .flatMapMany(response -> Flux.fromIterable(response.value().pageRange())); } @@ -333,13 +394,21 @@ public Flux getPageRangesDiff(BlobRange blobRange, String prevSnapsho * @param accessConditions * {@link BlobAccessConditions} * - * @return - * A reactive response emitting all the different page ranges. + * @return A reactive response emitting all the different page ranges. + * @throws IllegalArgumentException If {@code prevSnapshot} is {@code null} */ - public Flux getPageRangesDiff(BlobRange blobRange, String prevSnapshot, - BlobAccessConditions accessConditions) { - return pageBlobAsyncRawClient - .getPageRangesDiff(blobRange, prevSnapshot, accessConditions) + public Flux getPageRangesDiff(BlobRange blobRange, String prevSnapshot, BlobAccessConditions accessConditions) { + blobRange = blobRange == null ? new BlobRange(0) : blobRange; + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + if (prevSnapshot == null) { + throw new IllegalArgumentException("prevSnapshot cannot be null"); + } + + return postProcessResponse(this.azureBlobStorage.pageBlobs().getPageRangesDiffWithRestResponseAsync( + null, null, snapshot, null, null, prevSnapshot, + blobRange.toHeaderValue(), null, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .flatMapMany(response -> Flux.fromIterable(response.value().pageRange())); } @@ -368,12 +437,20 @@ public Mono> resize(long size) { * @param accessConditions * {@link BlobAccessConditions} * - * @return - * A reactive response emitting the resized page blob. + * @return A reactive response emitting the resized page blob. + * @throws IllegalArgumentException If {@code size} isn't a multiple of {@link PageBlobAsyncClient#PAGE_BYTES} */ public Mono> resize(long size, BlobAccessConditions accessConditions) { - return pageBlobAsyncRawClient - .resize(size, accessConditions) + if (size % PAGE_BYTES != 0) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("size must be a multiple of PageBlobAsyncClient.PAGE_BYTES."); + } + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + + return postProcessResponse(this.azureBlobStorage.pageBlobs().resizeWithRestResponseAsync(null, + null, size, null, null, accessConditions.leaseAccessConditions(), + accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new PageBlobItem(rb.deserializedHeaders()))); } @@ -407,13 +484,22 @@ public Mono> updateSequenceNumber(SequenceNumberActionTyp * @param accessConditions * {@link BlobAccessConditions} * - * @return - * A reactive response emitting the updated page blob. + * @return A reactive response emitting the updated page blob. + * @throws IllegalArgumentException If {@code sequenceNumber} isn't null and is less than 0 */ - public Mono> updateSequenceNumber(SequenceNumberActionType action, - Long sequenceNumber, BlobAccessConditions accessConditions) { - return pageBlobAsyncRawClient - .updateSequenceNumber(action, sequenceNumber, accessConditions) + public Mono> updateSequenceNumber(SequenceNumberActionType action, Long sequenceNumber, BlobAccessConditions accessConditions) { + if (sequenceNumber != null && sequenceNumber < 0) { + // Throwing is preferred to Single.error because this will error out immediately instead of waiting until + // subscription. + throw new IllegalArgumentException("SequenceNumber must be greater than or equal to 0."); + } + accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; + sequenceNumber = action == SequenceNumberActionType.INCREMENT ? null : sequenceNumber; + + return postProcessResponse( + this.azureBlobStorage.pageBlobs().updateSequenceNumberWithRestResponseAsync(null, + null, action, null, sequenceNumber, null, + accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new PageBlobItem(rb.deserializedHeaders()))); } @@ -454,13 +540,37 @@ public Mono> copyIncremental(URL source, String snapsho * to construct conditions related to when the blob was changed relative to the given request. The request * will fail if the specified condition is not satisfied. * - * @return - * A reactive response emitting the copy status. + * @return A reactive response emitting the copy status. + * @throws Error If {@code source} and {@code snapshot} form a malformed URL. */ - public Mono> copyIncremental(URL source, String snapshot, - ModifiedAccessConditions modifiedAccessConditions) { - return pageBlobAsyncRawClient - .copyIncremental(source, snapshot, modifiedAccessConditions) + public Mono> copyIncremental(URL source, String snapshot, ModifiedAccessConditions modifiedAccessConditions) { + UrlBuilder builder = UrlBuilder.parse(source); + builder.setQueryParameter(Constants.SNAPSHOT_QUERY_PARAMETER, snapshot); + try { + source = builder.toURL(); + } catch (MalformedURLException e) { + // We are parsing a valid url and adding a query parameter. If this fails, we can't recover. + throw new Error(e); + } + return postProcessResponse(this.azureBlobStorage.pageBlobs().copyIncrementalWithRestResponseAsync( + null, null, source, null, null, modifiedAccessConditions, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.deserializedHeaders().copyStatus())); } + + private static String pageRangeToString(PageRange pageRange) { + if (pageRange.start() < 0 || pageRange.end() <= 0) { + throw new IllegalArgumentException("PageRange's start and end values must be greater than or equal to " + + "0 if specified."); + } + if (pageRange.start() % PAGE_BYTES != 0) { + throw new IllegalArgumentException("PageRange's start value must be a multiple of 512."); + } + if (pageRange.end() % PAGE_BYTES != PAGE_BYTES - 1) { + throw new IllegalArgumentException("PageRange's end value must be 1 less than a multiple of 512."); + } + if (pageRange.end() <= pageRange.start()) { + throw new IllegalArgumentException("PageRange's End value must be after the start."); + } + return "bytes=" + pageRange.start() + '-' + pageRange.end(); + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncRawClient.java deleted file mode 100644 index fba55bd8d7455..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobAsyncRawClient.java +++ /dev/null @@ -1,620 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.implementation.http.UrlBuilder; -import com.azure.core.util.Context; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.BlobAccessConditions; -import com.azure.storage.blob.models.BlobHTTPHeaders; -import com.azure.storage.blob.models.BlobRange; -import com.azure.storage.blob.models.Metadata; -import com.azure.storage.blob.models.ModifiedAccessConditions; -import com.azure.storage.blob.models.PageBlobAccessConditions; -import com.azure.storage.blob.models.PageBlobsClearPagesResponse; -import com.azure.storage.blob.models.PageBlobsCopyIncrementalResponse; -import com.azure.storage.blob.models.PageBlobsCreateResponse; -import com.azure.storage.blob.models.PageBlobsGetPageRangesDiffResponse; -import com.azure.storage.blob.models.PageBlobsGetPageRangesResponse; -import com.azure.storage.blob.models.PageBlobsResizeResponse; -import com.azure.storage.blob.models.PageBlobsUpdateSequenceNumberResponse; -import com.azure.storage.blob.models.PageBlobsUploadPagesFromURLResponse; -import com.azure.storage.blob.models.PageBlobsUploadPagesResponse; -import com.azure.storage.blob.models.PageRange; -import com.azure.storage.blob.models.SequenceNumberActionType; -import com.azure.storage.blob.models.SourceModifiedAccessConditions; -import io.netty.buffer.ByteBuf; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.net.MalformedURLException; -import java.net.URL; - -import static com.azure.storage.blob.Utility.postProcessResponse; - -/** - * Represents a URL to a page blob. It may be obtained by direct construction or via the create method on a - * {@link ContainerAsyncClient} object. This class does not hold any state about a particular blob but is instead a convenient - * way of sending off appropriate requests to the resource on the service. Please refer to the - * Azure Docs - * for more information. - */ -final class PageBlobAsyncRawClient extends BlobAsyncRawClient { - - /** - * Indicates the number of bytes in a page. - */ - public static final int PAGE_BYTES = 512; - - /** - * Indicates the maximum number of bytes that may be sent in a call to putPage. - */ - public static final int MAX_PUT_PAGES_BYTES = 4 * Constants.MB; - - /** - * Creates a {@code PageBlobAsyncRawClient} object pointing to the account specified by the URL and using the provided - * pipeline to make HTTP requests. - * - */ - PageBlobAsyncRawClient(AzureBlobStorageImpl azureBlobStorage, String snapshot) { - super(azureBlobStorage, snapshot); - } - - private static String pageRangeToString(PageRange pageRange) { - if (pageRange.start() < 0 || pageRange.end() <= 0) { - throw new IllegalArgumentException("PageRange's start and end values must be greater than or equal to " - + "0 if specified."); - } - if (pageRange.start() % PageBlobAsyncRawClient.PAGE_BYTES != 0) { - throw new IllegalArgumentException("PageRange's start value must be a multiple of 512."); - } - if (pageRange.end() % PageBlobAsyncRawClient.PAGE_BYTES != PageBlobAsyncRawClient.PAGE_BYTES - 1) { - throw new IllegalArgumentException("PageRange's end value must be 1 less than a multiple of 512."); - } - if (pageRange.end() <= pageRange.start()) { - throw new IllegalArgumentException("PageRange's End value must be after the start."); - } - return new StringBuilder("bytes=").append(pageRange.start()).append('-').append(pageRange.end()).toString(); - } - - /** - * Creates a page blob of the specified length. Call PutPage to upload data data to a page blob. - * For more information, see the - * Azure Docs. - * - * @param size - * Specifies the maximum size for the page blob, up to 8 TB. The page blob size must be aligned to a - * 512-byte boundary. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono create(long size) { - return this.create(size, null, null, null, null); - } - - /** - * Creates a page blob of the specified length. Call PutPage to upload data data to a page blob. - * For more information, see the - * Azure Docs. - * - * @param size - * Specifies the maximum size for the page blob, up to 8 TB. The page blob size must be aligned to a - * 512-byte boundary. - * @param sequenceNumber - * A user-controlled value that you can use to track requests. The value of the sequence number must be - * between 0 and 2^63 - 1.The default value is 0. - * @param headers - * {@link BlobHTTPHeaders} - * @param metadata - * {@link Metadata} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code size} isn't a multiple of {@link PageBlobAsyncRawClient#PAGE_BYTES} - * or {@code sequenceNumber} isn't null and is less than 0. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.create")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono create(long size, Long sequenceNumber, BlobHTTPHeaders headers, - Metadata metadata, BlobAccessConditions accessConditions) { - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - if (size % PageBlobAsyncRawClient.PAGE_BYTES != 0) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("size must be a multiple of PageBlobAsyncRawClient.PAGE_BYTES."); - } - if (sequenceNumber != null && sequenceNumber < 0) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("SequenceNumber must be greater than or equal to 0."); - } - metadata = metadata == null ? new Metadata() : metadata; - - return postProcessResponse(this.azureBlobStorage.pageBlobs().createWithRestResponseAsync(null, - null, 0, size, null, metadata, null, null, - null, sequenceNumber, null, headers, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Writes 1 or more pages to the page blob. The start and end offsets must be a multiple of 512. - * For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - * - * @param pageRange - * A {@link PageRange} object. Given that pages must be aligned with 512-byte boundaries, the start offset must - * be a modulus of 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges are - * 0-511, 512-1023, etc. - * @param body - * The data to upload. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.uploadPages")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono uploadPages(PageRange pageRange, Flux body) { - return this.uploadPages(pageRange, body, null); - } - - /** - * Writes 1 or more pages to the page blob. The start and end offsets must be a multiple of 512. - * For more information, see the - * Azure Docs. - *

- * Note that the data passed must be replayable if retries are enabled (the default). In other words, the - * {@code Flux} must produce the same data each time it is subscribed to. - * - * @param pageRange - * A {@link PageRange} object. Given that pages must be aligned with 512-byte boundaries, the start offset - * must be a modulus of 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges - * are 0-511, 512-1023, etc. - * @param body - * The data to upload. Note that this {@code Flux} must be replayable if retries are enabled - * (the default). In other words, the Flowable must produce the same data each time it is subscribed to. - * @param pageBlobAccessConditions - * {@link PageBlobAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code pageRange} is {@code null} - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.uploadPages")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono uploadPages(PageRange pageRange, Flux body, - PageBlobAccessConditions pageBlobAccessConditions) { - pageBlobAccessConditions = pageBlobAccessConditions == null ? new PageBlobAccessConditions() - : pageBlobAccessConditions; - - if (pageRange == null) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("pageRange cannot be null."); - } - String pageRangeStr = pageRangeToString(pageRange); - - return postProcessResponse(this.azureBlobStorage.pageBlobs().uploadPagesWithRestResponseAsync(null, - null, body, pageRange.end() - pageRange.start() + 1, null, - null, pageRangeStr, null, null, null, null, - pageBlobAccessConditions.leaseAccessConditions(), pageBlobAccessConditions.sequenceNumberAccessConditions(), - pageBlobAccessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Writes 1 or more pages from the source page blob to this page blob. The start and end offsets must be a multiple - * of 512. - * For more information, see the - * Azure Docs. - *

- * - * @param range - * A {@link PageRange} object. Given that pages must be aligned with 512-byte boundaries, the start offset - * must be a modulus of 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges - * are 0-511, 512-1023, etc. - * @param sourceURL - * The url to the blob that will be the source of the copy. A source blob in the same storage account can be - * authenticated via Shared Key. However, if the source is a blob in another account, the source blob must - * either be public or must be authenticated via a shared access signature. If the source blob is public, no - * authentication is required to perform the operation. - * @param sourceOffset - * The source offset to copy from. Pass null or 0 to copy from the beginning of source page blob. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_from_url "Sample code for PageBlobAsyncRawClient.uploadPagesFromURL")] - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono uploadPagesFromURL(PageRange range, URL sourceURL, Long sourceOffset) { - return this.uploadPagesFromURL(range, sourceURL, sourceOffset, null, null, - null); - } - - /** - * Writes 1 or more pages from the source page blob to this page blob. The start and end offsets must be a multiple - * of 512. - * For more information, see the - * Azure Docs. - *

- * - * @param range - * The destination {@link PageRange} range. Given that pages must be aligned with 512-byte boundaries, the start offset - * must be a modulus of 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges - * are 0-511, 512-1023, etc. - * @param sourceURL - * The url to the blob that will be the source of the copy. A source blob in the same storage account can be - * authenticated via Shared Key. However, if the source is a blob in another account, the source blob must - * either be public or must be authenticated via a shared access signature. If the source blob is public, no - * authentication is required to perform the operation. - * @param sourceOffset - * The source offset to copy from. Pass null or 0 to copy from the beginning of source blob. - * @param sourceContentMD5 - * An MD5 hash of the block content from the source blob. If specified, the service will calculate the MD5 - * of the received data and fail the request if it does not match the provided MD5. - * @param destAccessConditions - * {@link PageBlobAccessConditions} - * @param sourceAccessConditions - * {@link SourceModifiedAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code range} is {@code null} - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_from_url "Sample code for PageBlobAsyncRawClient.uploadPagesFromURL")] - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono uploadPagesFromURL(PageRange range, URL sourceURL, Long sourceOffset, - byte[] sourceContentMD5, PageBlobAccessConditions destAccessConditions, - SourceModifiedAccessConditions sourceAccessConditions) { - - if (range == null) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("range cannot be null."); - } - - String rangeString = pageRangeToString(range); - - if (sourceOffset == null) { - sourceOffset = 0L; - } - - String sourceRangeString = pageRangeToString(new PageRange().start(sourceOffset).end(sourceOffset + (range.end() - range.start()))); - - destAccessConditions = destAccessConditions == null ? new PageBlobAccessConditions() : destAccessConditions; - - return postProcessResponse(this.azureBlobStorage.pageBlobs().uploadPagesFromURLWithRestResponseAsync( - null, null, sourceURL, sourceRangeString, 0, rangeString, sourceContentMD5, - null, null, destAccessConditions.leaseAccessConditions(), - destAccessConditions.sequenceNumberAccessConditions(), destAccessConditions.modifiedAccessConditions(), - sourceAccessConditions, Context.NONE)); - } - - /** - * Frees the specified pages from the page blob. - * For more information, see the - * Azure Docs. - * - * @param pageRange - * A {@link PageRange} object. Given that pages must be aligned with 512-byte boundaries, the start offset - * must be a modulus of 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges - * are 0-511, 512-1023, etc. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.clearPages")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono clearPages(PageRange pageRange) { - return this.clearPages(pageRange, null); - } - - /** - * Frees the specified pages from the page blob. - * For more information, see the - * Azure Docs. - * - * @param pageRange - * A {@link PageRange} object. Given that pages must be aligned with 512-byte boundaries, the start offset - * must be a modulus of 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges - * are 0-511, 512-1023, etc. - * @param pageBlobAccessConditions - * {@link PageBlobAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code pageRange} is {@code null} - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.clearPages")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono clearPages(PageRange pageRange, - PageBlobAccessConditions pageBlobAccessConditions) { - pageBlobAccessConditions = pageBlobAccessConditions == null ? new PageBlobAccessConditions() - : pageBlobAccessConditions; - if (pageRange == null) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("pageRange cannot be null."); - } - String pageRangeStr = pageRangeToString(pageRange); - - return postProcessResponse(this.azureBlobStorage.pageBlobs().clearPagesWithRestResponseAsync(null, - null, 0, null, pageRangeStr, null, - pageBlobAccessConditions.leaseAccessConditions(), pageBlobAccessConditions.sequenceNumberAccessConditions(), - pageBlobAccessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Returns the list of valid page ranges for a page blob or snapshot of a page blob. - * For more information, see the Azure Docs. - * - * @param blobRange - * {@link BlobRange} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.getPageRanges")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getPageRanges(BlobRange blobRange) { - return this.getPageRanges(blobRange, null); - } - - /** - * Returns the list of valid page ranges for a page blob or snapshot of a page blob. - * For more information, see the Azure Docs. - * - * @param blobRange - * {@link BlobRange} - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.getPageRanges")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getPageRanges(BlobRange blobRange, - BlobAccessConditions accessConditions) { - blobRange = blobRange == null ? new BlobRange(0) : blobRange; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.pageBlobs().getPageRangesWithRestResponseAsync( - null, null, snapshot, null, null, blobRange.toHeaderValue(), - null, accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), - Context.NONE)); - } - - /** - * Gets the collection of page ranges that differ between a specified snapshot and this page blob. - * For more information, see the Azure Docs. - * - * @param blobRange - * {@link BlobRange} - * @param prevSnapshot - * Specifies that the response will contain only pages that were changed between target blob and previous - * snapshot. Changed pages include both updated and cleared pages. The target - * blob may be a snapshot, as long as the snapshot specified by prevsnapshot is the older of the two. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_diff "Sample code for PageBlobAsyncRawClient.getPageRangesDiff")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getPageRangesDiff(BlobRange blobRange, String prevSnapshot) { - return this.getPageRangesDiff(blobRange, prevSnapshot, null); - } - - /** - * Gets the collection of page ranges that differ between a specified snapshot and this page blob. - * For more information, see the Azure Docs. - * - * @param blobRange - * {@link BlobRange} - * @param prevSnapshot - * Specifies that the response will contain only pages that were changed between target blob and previous - * snapshot. Changed pages include both updated and cleared pages. The target - * blob may be a snapshot, as long as the snapshot specified by prevsnapshot is the older of the two. - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code prevSnapshot} is {@code null} - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_diff "Sample code for PageBlobAsyncRawClient.getPageRangesDiff")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getPageRangesDiff(BlobRange blobRange, String prevSnapshot, - BlobAccessConditions accessConditions) { - blobRange = blobRange == null ? new BlobRange(0) : blobRange; - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - if (prevSnapshot == null) { - throw new IllegalArgumentException("prevSnapshot cannot be null"); - } - - return postProcessResponse(this.azureBlobStorage.pageBlobs().getPageRangesDiffWithRestResponseAsync( - null, null, snapshot, null, null, prevSnapshot, - blobRange.toHeaderValue(), null, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Resizes the page blob to the specified size (which must be a multiple of 512). - * For more information, see the Azure Docs. - * - * @param size - * Resizes a page blob to the specified size. If the specified value is less than the current size of the - * blob, then all pages above the specified value are cleared. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.resize")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono resize(long size) { - return this.resize(size, null); - } - - /** - * Resizes the page blob to the specified size (which must be a multiple of 512). - * For more information, see the Azure Docs. - * - * @param size - * Resizes a page blob to the specified size. If the specified value is less than the current size of the - * blob, then all pages above the specified value are cleared. - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code size} isn't a multiple of {@link PageBlobAsyncRawClient#PAGE_BYTES} - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.resize")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono resize(long size, BlobAccessConditions accessConditions) { - if (size % PageBlobAsyncRawClient.PAGE_BYTES != 0) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("size must be a multiple of PageBlobAsyncRawClient.PAGE_BYTES."); - } - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - - return postProcessResponse(this.azureBlobStorage.pageBlobs().resizeWithRestResponseAsync(null, - null, size, null, null, accessConditions.leaseAccessConditions(), - accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Sets the page blob's sequence number. - * For more information, see the Azure Docs. - * - * @param action - * Indicates how the service should modify the blob's sequence number. - * @param sequenceNumber - * The blob's sequence number. The sequence number is a user-controlled property that you can use to track - * requests and manage concurrency issues. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.updateSequenceNumber")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono updateSequenceNumber(SequenceNumberActionType action, - Long sequenceNumber) { - return this.updateSequenceNumber(action, sequenceNumber, null); - } - - /** - * Sets the page blob's sequence number. - * For more information, see the Azure Docs. - * - * @param action - * Indicates how the service should modify the blob's sequence number. - * @param sequenceNumber - * The blob's sequence number. The sequence number is a user-controlled property that you can use to track - * requests and manage concurrency issues. - * @param accessConditions - * {@link BlobAccessConditions} - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code sequenceNumber} isn't null and is less than 0 - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=page_blob_basic "Sample code for PageBlobAsyncRawClient.updateSequenceNumber")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono updateSequenceNumber(SequenceNumberActionType action, - Long sequenceNumber, BlobAccessConditions accessConditions) { - if (sequenceNumber != null && sequenceNumber < 0) { - // Throwing is preferred to Single.error because this will error out immediately instead of waiting until - // subscription. - throw new IllegalArgumentException("SequenceNumber must be greater than or equal to 0."); - } - accessConditions = accessConditions == null ? new BlobAccessConditions() : accessConditions; - sequenceNumber = action == SequenceNumberActionType.INCREMENT ? null : sequenceNumber; - - return postProcessResponse( - this.azureBlobStorage.pageBlobs().updateSequenceNumberWithRestResponseAsync(null, - null, action, null, sequenceNumber, null, - accessConditions.leaseAccessConditions(), accessConditions.modifiedAccessConditions(), Context.NONE)); - } - - /** - * Begins an operation to start an incremental copy from one page blob's snapshot to this page - * blob. The snapshot is copied such that only the differential changes between the previously copied snapshot are - * transferred to the destination. The copied snapshots are complete copies of the original snapshot and can be read - * or copied from as usual. For more information, see - * the Azure Docs here and - * here. - * - * @param source - * The source page blob. - * @param snapshot - * The snapshot on the copy source. - * - * @return Emits the successful response. - */ - public Mono copyIncremental(URL source, String snapshot) { - return this.copyIncremental(source, snapshot, null); - } - - /** - * Begins an operation to start an incremental copy from one page blob's snapshot to this page - * blob. The snapshot is copied such that only the differential changes between the previously copied snapshot are - * transferred to the destination. The copied snapshots are complete copies of the original snapshot and can be read - * or copied from as usual. For more information, see - * the Azure Docs here and - * here. - * - * @param source - * The source page blob. - * @param snapshot - * The snapshot on the copy source. - * @param modifiedAccessConditions - * Standard HTTP Access conditions related to the modification of data. ETag and LastModifiedTime are used - * to construct conditions related to when the blob was changed relative to the given request. The request - * will fail if the specified condition is not satisfied. - * - * @return Emits the successful response. - * @throws Error If {@code source} and {@code snapshot} form a malformed URL. - */ - public Mono copyIncremental(URL source, String snapshot, - ModifiedAccessConditions modifiedAccessConditions) { - - UrlBuilder builder = UrlBuilder.parse(source); - builder.setQueryParameter(Constants.SNAPSHOT_QUERY_PARAMETER, snapshot); - try { - source = builder.toURL(); - } catch (MalformedURLException e) { - // We are parsing a valid url and adding a query parameter. If this fails, we can't recover. - throw new Error(e); - } - return postProcessResponse(this.azureBlobStorage.pageBlobs().copyIncrementalWithRestResponseAsync( - null, null, source, null, null, modifiedAccessConditions, Context.NONE)); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClient.java index 284f74d0216ac..62d2146ea8d61 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClient.java @@ -15,13 +15,14 @@ import com.azure.storage.blob.models.PageRange; import com.azure.storage.blob.models.SequenceNumberActionType; import com.azure.storage.blob.models.SourceModifiedAccessConditions; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import java.io.InputStream; import java.net.URL; -import java.nio.ByteBuffer; import java.time.Duration; /** @@ -45,12 +46,12 @@ public final class PageBlobClient extends BlobClient { /** * Indicates the number of bytes in a page. */ - public static final int PAGE_BYTES = 512; + public static final int PAGE_BYTES = PageBlobAsyncClient.PAGE_BYTES; /** * Indicates the maximum number of bytes that may be sent in a call to putPage. */ - public static final int MAX_PUT_PAGES_BYTES = 4 * Constants.MB; + public static final int MAX_PUT_PAGES_BYTES = PageBlobAsyncClient.MAX_PUT_PAGES_BYTES; /** * Package-private constructor for use by {@link PageBlobClientBuilder}. @@ -189,7 +190,7 @@ public Response uploadPages(PageRange pageRange, InputStream body) public Response uploadPages(PageRange pageRange, InputStream body, PageBlobAccessConditions pageBlobAccessConditions, Duration timeout) { long length = pageRange.end() - pageRange.start(); - Flux fbb = Flux.range(0, (int) Math.ceil((double) length / (double) PAGE_BYTES)) + Flux fbb = Flux.range(0, (int) Math.ceil((double) length / (double) PAGE_BYTES)) .map(i -> i * PAGE_BYTES) .concatMap(pos -> Mono.fromCallable(() -> { byte[] cache = new byte[PAGE_BYTES]; @@ -197,7 +198,8 @@ public Response uploadPages(PageRange pageRange, InputStream body, while (read < PAGE_BYTES) { read += body.read(cache, read, PAGE_BYTES - read); } - return ByteBuffer.wrap(cache); + + return ByteBufAllocator.DEFAULT.buffer(read).writeBytes(cache); })); Mono> response = pageBlobAsyncClient.uploadPages(pageRange, diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java index 114c28eb71209..f6b4767cada56 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java @@ -12,10 +12,10 @@ import java.time.OffsetDateTime; /** - * ServiceSASSignatureValues is used to generate a Shared Access Signature (SAS) for an Azure Storage service. Once - * all the values here are set appropriately, call generateSASQueryParameters to obtain a representation of the SAS - * which can actually be applied to blob urls. Note: that both this class and {@link SASQueryParameters} exist because - * the former is mutable and a logical representation while the latter is immutable and used to generate actual REST + * ServiceSASSignatureValues is used to generate a Shared Access Signature (SAS) for an Azure Storage service. Once all + * the values here are set appropriately, call generateSASQueryParameters to obtain a representation of the SAS which + * can actually be applied to blob urls. Note: that both this class and {@link SASQueryParameters} exist because the + * former is mutable and a logical representation while the latter is immutable and used to generate actual REST * requests. *

* Please see here @@ -24,9 +24,9 @@ * Please see here for * more details on each value, including which are required. * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_sas "Sample code for ServiceSASSignatureValues")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) + * @apiNote ## Sample Code \n [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_sas + * "Sample code for ServiceSASSignatureValues")] \n For more samples, please see the [Samples + * file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) */ final class ServiceSASSignatureValues { @@ -68,6 +68,7 @@ final class ServiceSASSignatureValues { /** * Creates an object with the specified expiry time and permissions + * * @param expiryTime * @param permissions */ @@ -78,6 +79,7 @@ final class ServiceSASSignatureValues { /** * Creates an object with the specified identifier + * * @param identifier */ ServiceSASSignatureValues(String identifier) { @@ -85,8 +87,8 @@ final class ServiceSASSignatureValues { } ServiceSASSignatureValues(String version, SASProtocol sasProtocol, OffsetDateTime startTime, - OffsetDateTime expiryTime, String permission, IPRange ipRange, String identifier, String cacheControl, - String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { + OffsetDateTime expiryTime, String permission, IPRange ipRange, String identifier, String cacheControl, + String contentDisposition, String contentEncoding, String contentLanguage, String contentType) { if (version != null) { this.version = version; } @@ -229,6 +231,7 @@ public ServiceSASSignatureValues canonicalName(String canonicalName) { /** * The canonical name of the object the SAS user may access. + * * @throws RuntimeException If urlString is a malformed URL. */ public ServiceSASSignatureValues canonicalName(String urlString, String accountName) { @@ -359,9 +362,7 @@ public ServiceSASSignatureValues contentType(String contentType) { * Uses an account's shared key credential to sign these signature values to produce the proper SAS query * parameters. * - * @param sharedKeyCredentials - * A {@link SharedKeyCredential} object used to sign the SAS values. - * + * @param sharedKeyCredentials A {@link SharedKeyCredential} object used to sign the SAS values. * @return {@link SASQueryParameters} * @throws Error If the accountKey is not a valid Base64-encoded string. */ @@ -380,17 +381,15 @@ public SASQueryParameters generateSASQueryParameters(SharedKeyCredential sharedK } return new SASQueryParameters(this.version, null, null, - this.protocol, this.startTime, this.expiryTime, this.ipRange, this.identifier, resource, - this.permissions, signature, this.cacheControl, this.contentDisposition, this.contentEncoding, - this.contentLanguage, this.contentType, null /* delegate */); + this.protocol, this.startTime, this.expiryTime, this.ipRange, this.identifier, resource, + this.permissions, signature, this.cacheControl, this.contentDisposition, this.contentEncoding, + this.contentLanguage, this.contentType, null /* delegate */); } /** * Uses a user delegation key to sign these signature values to produce the proper SAS query parameters. * - * @param delegationKey - * A {@link UserDelegationKey} object used to sign the SAS values. - * + * @param delegationKey A {@link UserDelegationKey} object used to sign the SAS values. * @return {@link SASQueryParameters} * @throws Error If the accountKey is not a valid Base64-encoded string. */ @@ -409,9 +408,9 @@ public SASQueryParameters generateSASQueryParameters(UserDelegationKey delegatio } return new SASQueryParameters(this.version, null, null, - this.protocol, this.startTime, this.expiryTime, this.ipRange, null /* identifier */, resource, - this.permissions, signature, this.cacheControl, this.contentDisposition, this.contentEncoding, - this.contentLanguage, this.contentType, delegationKey); + this.protocol, this.startTime, this.expiryTime, this.ipRange, null /* identifier */, resource, + this.permissions, signature, this.cacheControl, this.contentDisposition, this.contentEncoding, + this.contentLanguage, this.contentType, delegationKey); } /** @@ -441,46 +440,46 @@ private void assertGenerateOK(boolean usingUserDelegation) { private String stringToSign() { return String.join("\n", - this.permissions == null ? "" : this.permissions, - this.startTime == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), - this.expiryTime == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), - this.canonicalName == null ? "" : this.canonicalName, - this.identifier == null ? "" : this.identifier, - this.ipRange == null ? (new IPRange()).toString() : this.ipRange.toString(), - this.protocol == null ? "" : protocol.toString(), - this.version == null ? "" : this.version, - this.resource == null ? "" : this.resource, - this.snapshotId == null ? "" : this.snapshotId, - this.cacheControl == null ? "" : this.cacheControl, - this.contentDisposition == null ? "" : this.contentDisposition, - this.contentEncoding == null ? "" : this.contentEncoding, - this.contentLanguage == null ? "" : this.contentLanguage, - this.contentType == null ? "" : this.contentType + this.permissions == null ? "" : this.permissions, + this.startTime == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), + this.expiryTime == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), + this.canonicalName == null ? "" : this.canonicalName, + this.identifier == null ? "" : this.identifier, + this.ipRange == null ? (new IPRange()).toString() : this.ipRange.toString(), + this.protocol == null ? "" : protocol.toString(), + this.version == null ? "" : this.version, + this.resource == null ? "" : this.resource, + this.snapshotId == null ? "" : this.snapshotId, + this.cacheControl == null ? "" : this.cacheControl, + this.contentDisposition == null ? "" : this.contentDisposition, + this.contentEncoding == null ? "" : this.contentEncoding, + this.contentLanguage == null ? "" : this.contentLanguage, + this.contentType == null ? "" : this.contentType ); } private String stringToSign(final UserDelegationKey key) { return String.join("\n", - this.permissions == null ? "" : this.permissions, - this.startTime == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), - this.expiryTime == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), - this.canonicalName == null ? "" : this.canonicalName, - key.signedOid() == null ? "" : key.signedOid(), - key.signedTid() == null ? "" : key.signedTid(), - key.signedStart() == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(key.signedStart()), - key.signedExpiry() == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(key.signedExpiry()), - key.signedService() == null ? "" : key.signedService(), - key.signedVersion() == null ? "" : key.signedVersion(), - this.ipRange == null ? new IPRange().toString() : this.ipRange.toString(), - this.protocol == null ? "" : this.protocol.toString(), - this.version == null ? "" : this.version, - this.resource == null ? "" : this.resource, - this.snapshotId == null ? "" : this.snapshotId, - this.cacheControl == null ? "" : this.cacheControl, - this.contentDisposition == null ? "" : this.contentDisposition, - this.contentEncoding == null ? "" : this.contentEncoding, - this.contentLanguage == null ? "" : this.contentLanguage, - this.contentType == null ? "" : this.contentType + this.permissions == null ? "" : this.permissions, + this.startTime == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), + this.expiryTime == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), + this.canonicalName == null ? "" : this.canonicalName, + key.signedOid() == null ? "" : key.signedOid(), + key.signedTid() == null ? "" : key.signedTid(), + key.signedStart() == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(key.signedStart()), + key.signedExpiry() == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(key.signedExpiry()), + key.signedService() == null ? "" : key.signedService(), + key.signedVersion() == null ? "" : key.signedVersion(), + this.ipRange == null ? new IPRange().toString() : this.ipRange.toString(), + this.protocol == null ? "" : this.protocol.toString(), + this.version == null ? "" : this.version, + this.resource == null ? "" : this.resource, + this.snapshotId == null ? "" : this.snapshotId, + this.cacheControl == null ? "" : this.cacheControl, + this.contentDisposition == null ? "" : this.contentDisposition, + this.contentEncoding == null ? "" : this.contentEncoding, + this.contentLanguage == null ? "" : this.contentLanguage, + this.contentType == null ? "" : this.contentType ); } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java index c6c48d7e5770d..b93ee7bd579e7 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncClient.java @@ -8,8 +8,11 @@ import com.azure.core.http.rest.Response; import com.azure.core.http.rest.SimpleResponse; import com.azure.core.http.rest.VoidResponse; +import com.azure.core.util.Context; import com.azure.storage.blob.implementation.AzureBlobStorageBuilder; +import com.azure.storage.blob.implementation.AzureBlobStorageImpl; import com.azure.storage.blob.models.ContainerItem; +import com.azure.storage.blob.models.KeyInfo; import com.azure.storage.blob.models.ListContainersOptions; import com.azure.storage.blob.models.Metadata; import com.azure.storage.blob.models.PublicAccessType; @@ -26,11 +29,12 @@ import java.net.URL; import java.time.OffsetDateTime; +import static com.azure.storage.blob.Utility.postProcessResponse; + /** - * Client to a storage account. It may only be instantiated through a {@link StorageClientBuilder}. - * This class does not hold any state about a particular storage account but is - * instead a convenient way of sending off appropriate requests to the resource on the service. - * It may also be used to construct URLs to blobs and containers. + * Client to a storage account. It may only be instantiated through a {@link StorageClientBuilder}. This class does not + * hold any state about a particular storage account but is instead a convenient way of sending off appropriate requests + * to the resource on the service. It may also be used to construct URLs to blobs and containers. * *

* This client contains operations on a blob. Operations on a container are available on {@link ContainerAsyncClient} @@ -41,36 +45,35 @@ * information on containers. * *

- * Note this client is an async client that returns reactive responses from Spring Reactor Core - * project (https://projectreactor.io/). Calling the methods in this client will NOT - * start the actual network operation, until {@code .subscribe()} is called on the reactive response. - * You can simply convert one of these responses to a {@link java.util.concurrent.CompletableFuture} - * object through {@link Mono#toFuture()}. + * Note this client is an async client that returns reactive responses from Spring Reactor Core project + * (https://projectreactor.io/). Calling the methods in this client will NOT start the actual network + * operation, until {@code .subscribe()} is called on the reactive response. You can simply convert one of these + * responses to a {@link java.util.concurrent.CompletableFuture} object through {@link Mono#toFuture()}. */ public final class StorageAsyncClient { - StorageAsyncRawClient storageAsyncRawClient; + private final AzureBlobStorageImpl azureBlobStorage; /** * Package-private constructor for use by {@link StorageClientBuilder}. + * * @param azureBlobStorageBuilder the API client builder for blob storage API */ StorageAsyncClient(AzureBlobStorageBuilder azureBlobStorageBuilder) { - this.storageAsyncRawClient = new StorageAsyncRawClient(azureBlobStorageBuilder.build()); + this.azureBlobStorage = azureBlobStorageBuilder.build(); } /** - * Initializes a {@link ContainerAsyncClient} object pointing to the specified container. This method does not create a - * container. It simply constructs the URL to the container and offers access to methods relevant to containers. + * Initializes a {@link ContainerAsyncClient} object pointing to the specified container. This method does not + * create a container. It simply constructs the URL to the container and offers access to methods relevant to + * containers. * - * @param containerName - * The name of the container to point to. - * @return - * A {@link ContainerAsyncClient} object pointing to the specified container + * @param containerName The name of the container to point to. + * @return A {@link ContainerAsyncClient} object pointing to the specified container */ public ContainerAsyncClient getContainerAsyncClient(String containerName) { return new ContainerAsyncClient(new AzureBlobStorageBuilder() .url(Utility.appendToURLPath(getAccountUrl(), containerName).toString()) - .pipeline(storageAsyncRawClient.azureBlobStorage.httpPipeline())); + .pipeline(azureBlobStorage.httpPipeline())); } /** @@ -91,11 +94,9 @@ public Mono> createContainer(String containerName * Azure Docs. * * @param containerName Name of the container to create - * @param metadata - * {@link Metadata} - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. + * @param metadata {@link Metadata} + * @param accessType Specifies how the data in this container is available to the public. See the + * x-ms-blob-public-access header in the Azure Docs for more information. Pass null for no public access. * @return A response containing a {@link ContainerAsyncClient} used to interact with the container created. */ public Mono> createContainer(String containerName, Metadata metadata, PublicAccessType accessType) { @@ -107,50 +108,76 @@ public Mono> createContainer(String containerName /** * Gets the URL of the storage account represented by this client. + * * @return the URL. * @throws RuntimeException If the account URL is malformed. */ public URL getAccountUrl() { try { - return new URL(storageAsyncRawClient.azureBlobStorage.url()); + return new URL(azureBlobStorage.url()); } catch (MalformedURLException e) { - throw new RuntimeException(String.format("Invalid URL on %s: %s" + getClass().getSimpleName(), storageAsyncRawClient.azureBlobStorage.url()), e); + throw new RuntimeException(String.format("Invalid URL on %s: %s" + getClass().getSimpleName(), azureBlobStorage.url()), e); } } /** - * Returns a reactive Publisher emitting all the containers in this account lazily as needed. For more information, see - * the Azure Docs. + * Returns a reactive Publisher emitting all the containers in this account lazily as needed. For more information, + * see the Azure Docs. * - * @return - * A reactive response emitting the list of containers. + * @return A reactive response emitting the list of containers. */ public Flux listContainers() { return this.listContainers(new ListContainersOptions()); } /** - * Returns a reactive Publisher emitting all the containers in this account lazily as needed. For more information, see + * Returns a reactive Publisher emitting all the containers in this account lazily as needed. For more information, + * see the Azure Docs. + * + * @param options A {@link ListContainersOptions} which specifies what data should be returned by the service. + * @return A reactive response emitting the list of containers. + */ + public Flux listContainers(ListContainersOptions options) { + return listContainersSegment(null, options) + .flatMapMany(response -> listContainersHelper(response.value().marker(), options, response)); + } + + /* + * Returns a Mono segment of containers starting from the specified Marker. + * Use an empty marker to start enumeration from the beginning. Container names are returned in lexicographic order. + * After getting a segment, process it, and then call ListContainers again (passing the the previously-returned + * Marker) to get the next segment. For more information, see * the Azure Docs. * + * @param marker + * Identifies the portion of the list to be returned with the next list operation. + * This value is returned in the response of a previous list operation as the + * ListContainersSegmentResponse.body().nextMarker(). Set to null to list the first segment. * @param options * A {@link ListContainersOptions} which specifies what data should be returned by the service. * - * @return - * A reactive response emitting the list of containers. + * @return Emits the successful response. + * + * @apiNote ## Sample Code \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list "Sample code for ServiceURL.listContainersSegment")] \n + * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list_helper "Helper code for ServiceURL.listContainersSegment")] \n + * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) */ - public Flux listContainers(ListContainersOptions options) { - return storageAsyncRawClient - .listContainersSegment(null, options) - .flatMapMany(response -> listContainersHelper(response.value().marker(), options, response)); + private Mono listContainersSegment(String marker, ListContainersOptions options) { + options = options == null ? new ListContainersOptions() : options; + + return postProcessResponse( + this.azureBlobStorage.services().listContainersSegmentWithRestResponseAsync( + options.prefix(), marker, options.maxResults(), options.details().toIncludeType(), null, + null, Context.NONE)); } private Flux listContainersHelper(String marker, ListContainersOptions options, - ServicesListContainersSegmentResponse response) { + ServicesListContainersSegmentResponse response) { Flux result = Flux.fromIterable(response.value().containerItems()); if (response.value().nextMarker() != null) { // Recursively add the continuation items to the observable. - result = result.concatWith(storageAsyncRawClient.listContainersSegment(marker, options) + result = result.concatWith(listContainersSegment(marker, options) .flatMapMany((r) -> listContainersHelper(response.value().nextMarker(), options, r))); } @@ -162,12 +189,11 @@ private Flux listContainersHelper(String marker, ListContainersOp * Gets the properties of a storage account’s Blob service. For more information, see the * Azure Docs. * - * @return - * A reactive response containing the storage account properties. + * @return A reactive response containing the storage account properties. */ public Mono> getProperties() { - return storageAsyncRawClient - .getProperties() + return postProcessResponse( + this.azureBlobStorage.services().getPropertiesWithRestResponseAsync(null, null, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.value())); } @@ -177,48 +203,50 @@ public Mono> getProperties() { * Note that setting the default service version has no effect when using this client because this client explicitly * sets the version header on each request, overriding the default. * - * @param properties - * Configures the service. - * - * @return - * A reactive response containing the storage account properties. + * @param properties Configures the service. + * @return A reactive response containing the storage account properties. */ public Mono setProperties(StorageServiceProperties properties) { - return storageAsyncRawClient - .setProperties(properties) + return postProcessResponse( + this.azureBlobStorage.services().setPropertiesWithRestResponseAsync(properties, null, null, Context.NONE)) .map(VoidResponse::new); } /** - * Gets a user delegation key for use with this account's blob storage. - * Note: This method call is only valid when using {@link TokenCredential} in this object's {@link HttpPipeline}. - * - * @param start - * Start time for the key's validity. Null indicates immediate start. - * @param expiry - * Expiration of the key's validity. + * Gets a user delegation key for use with this account's blob storage. Note: This method call is only valid when + * using {@link TokenCredential} in this object's {@link HttpPipeline}. * - * @return - * A reactive response containing the user delegation key. + * @param start Start time for the key's validity. Null indicates immediate start. + * @param expiry Expiration of the key's validity. + * @return A reactive response containing the user delegation key. + * @throws IllegalArgumentException If {@code start} isn't null and is after {@code expiry}. */ public Mono> getUserDelegationKey(OffsetDateTime start, OffsetDateTime expiry) { - return storageAsyncRawClient - .getUserDelegationKey(start, expiry) - .map(rb -> new SimpleResponse<>(rb, rb.value())); + Utility.assertNotNull("expiry", expiry); + if (start != null && !start.isBefore(expiry)) { + throw new IllegalArgumentException("`start` must be null or a datetime before `expiry`."); + } + + return postProcessResponse( + this.azureBlobStorage.services().getUserDelegationKeyWithRestResponseAsync( + new KeyInfo() + .start(start == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(start)) + .expiry(Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiry)), + null, null, Context.NONE) + ).map(rb -> new SimpleResponse<>(rb, rb.value())); } /** - * Retrieves statistics related to replication for the Blob service. It is only available on the secondary - * location endpoint when read-access geo-redundant replication is enabled for the storage account. For more - * information, see the + * Retrieves statistics related to replication for the Blob service. It is only available on the secondary location + * endpoint when read-access geo-redundant replication is enabled for the storage account. For more information, see + * the * Azure Docs. * - * @return - * A reactive response containing the storage account statistics. + * @return A reactive response containing the storage account statistics. */ public Mono> getStatistics() { - return storageAsyncRawClient - .getStatistics() + return postProcessResponse( + this.azureBlobStorage.services().getStatisticsWithRestResponseAsync(null, null, Context.NONE)) .map(rb -> new SimpleResponse<>(rb, rb.value())); } @@ -226,32 +254,24 @@ public Mono> getStatistics() { * Returns the sku name and account kind for the account. For more information, please see the * Azure Docs. * - * @return - * A reactive response containing the storage account info. + * @return A reactive response containing the storage account info. */ public Mono> getAccountInfo() { - return storageAsyncRawClient - .getAccountInfo() + return postProcessResponse(this.azureBlobStorage.services().getAccountInfoWithRestResponseAsync(Context.NONE)) .map(rb -> new SimpleResponse<>(rb, new StorageAccountInfo(rb.deserializedHeaders()))); } /** * Generates an account SAS token with the specified parameters * - * @param accountSASService - * The {@code AccountSASService} services for the account SAS - * @param accountSASResourceType - * An optional {@code AccountSASResourceType} resources for the account SAS - * @param accountSASPermission - * The {@code AccountSASPermission} permission for the account SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the account SAS - * - * @return - * A string that represents the SAS token + * @param accountSASService The {@code AccountSASService} services for the account SAS + * @param accountSASResourceType An optional {@code AccountSASResourceType} resources for the account SAS + * @param accountSASPermission The {@code AccountSASPermission} permission for the account SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the account SAS + * @return A string that represents the SAS token */ public String generateAccountSAS(AccountSASService accountSASService, AccountSASResourceType accountSASResourceType, - AccountSASPermission accountSASPermission, OffsetDateTime expiryTime) { + AccountSASPermission accountSASPermission, OffsetDateTime expiryTime) { return this.generateAccountSAS(accountSASService, accountSASResourceType, accountSASPermission, expiryTime, null /* startTime */, null /* version */, null /* ipRange */, null /* sasProtocol */); } @@ -259,29 +279,19 @@ public String generateAccountSAS(AccountSASService accountSASService, AccountSAS /** * Generates an account SAS token with the specified parameters * - * @param accountSASService - * The {@code AccountSASService} services for the account SAS - * @param accountSASResourceType - * An optional {@code AccountSASResourceType} resources for the account SAS - * @param accountSASPermission - * The {@code AccountSASPermission} permission for the account SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the account SAS - * @param startTime - * The {@code OffsetDateTime} start time for the account SAS - * @param version - * The {@code String} version for the account SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * - * @return - * A string that represents the SAS token + * @param accountSASService The {@code AccountSASService} services for the account SAS + * @param accountSASResourceType An optional {@code AccountSASResourceType} resources for the account SAS + * @param accountSASPermission The {@code AccountSASPermission} permission for the account SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the account SAS + * @param startTime The {@code OffsetDateTime} start time for the account SAS + * @param version The {@code String} version for the account SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @return A string that represents the SAS token */ public String generateAccountSAS(AccountSASService accountSASService, AccountSASResourceType accountSASResourceType, - AccountSASPermission accountSASPermission, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, IPRange ipRange, - SASProtocol sasProtocol) { + AccountSASPermission accountSASPermission, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, IPRange ipRange, + SASProtocol sasProtocol) { AccountSASSignatureValues accountSASSignatureValues = new AccountSASSignatureValues(); accountSASSignatureValues.services(accountSASService == null ? null : accountSASService.toString()); @@ -297,7 +307,7 @@ public String generateAccountSAS(AccountSASService accountSASService, AccountSAS accountSASSignatureValues.ipRange(ipRange); accountSASSignatureValues.protocol(sasProtocol); - SharedKeyCredential sharedKeyCredential = Utility.getSharedKeyCredential(this.storageAsyncRawClient.azureBlobStorage.httpPipeline()); + SharedKeyCredential sharedKeyCredential = Utility.getSharedKeyCredential(this.azureBlobStorage.httpPipeline()); SASQueryParameters sasQueryParameters = accountSASSignatureValues.generateSASQueryParameters(sharedKeyCredential); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncRawClient.java deleted file mode 100644 index a2b6be965ed30..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageAsyncRawClient.java +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.credentials.TokenCredential; -import com.azure.core.http.HttpPipeline; -import com.azure.core.util.Context; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.KeyInfo; -import com.azure.storage.blob.models.ListContainersOptions; -import com.azure.storage.blob.models.ServicesGetAccountInfoResponse; -import com.azure.storage.blob.models.ServicesGetPropertiesResponse; -import com.azure.storage.blob.models.ServicesGetStatisticsResponse; -import com.azure.storage.blob.models.ServicesGetUserDelegationKeyResponse; -import com.azure.storage.blob.models.ServicesListContainersSegmentResponse; -import com.azure.storage.blob.models.ServicesSetPropertiesResponse; -import com.azure.storage.blob.models.StorageServiceProperties; -import reactor.core.publisher.Mono; - -import java.time.OffsetDateTime; - -import static com.azure.storage.blob.Utility.postProcessResponse; - -/** - * Represents a URL to a storage service. This class does not hold any state about a particular storage account but is - * instead a convenient way of sending off appropriate requests to the resource on the service. - * It may also be used to construct URLs to blobs and containers. - * Please see here for more - * information on containers. - */ -final class StorageAsyncRawClient { - - final AzureBlobStorageImpl azureBlobStorage; - - /** - * Creates a {@code ServiceURL} object pointing to the account specified by the URL and using the provided pipeline - * to make HTTP requests. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_url "Sample code for ServiceURL constructor")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - StorageAsyncRawClient(AzureBlobStorageImpl azureBlobStorage) { - this.azureBlobStorage = azureBlobStorage; - } - - /** - * Returns a Mono segment of containers starting from the specified Marker. - * Use an empty marker to start enumeration from the beginning. Container names are returned in lexicographic order. - * After getting a segment, process it, and then call ListContainers again (passing the the previously-returned - * Marker) to get the next segment. For more information, see - * the Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListContainersSegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param options - * A {@link ListContainersOptions} which specifies what data should be returned by the service. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list "Sample code for ServiceURL.listContainersSegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list_helper "Helper code for ServiceURL.listContainersSegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono listContainersSegment(String marker, - ListContainersOptions options) { - options = options == null ? new ListContainersOptions() : options; - - return postProcessResponse( - this.azureBlobStorage.services().listContainersSegmentWithRestResponseAsync( - options.prefix(), marker, options.maxResults(), options.details().toIncludeType(), null, - null, Context.NONE)); - } - - /** - * Gets the properties of a storage account’s Blob service. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_getsetprops "Sample code for ServiceURL.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getProperties() { - return postProcessResponse( - this.azureBlobStorage.services().getPropertiesWithRestResponseAsync(null, null, Context.NONE)); - } - - /** - * Sets properties for a storage account's Blob service endpoint. For more information, see the - * Azure Docs. - * Note that setting the default service version has no effect when using this client because this client explicitly - * sets the version header on each request, overriding the default. - * - * @param properties - * Configures the service. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_getsetprops "Sample code for ServiceURL.setProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono setProperties(StorageServiceProperties properties) { - return postProcessResponse( - this.azureBlobStorage.services().setPropertiesWithRestResponseAsync(properties, null, null, Context.NONE)); - } - - /** - * Gets a user delegation key for use with this account's blob storage. - * Note: This method call is only valid when using {@link TokenCredential} in this object's {@link HttpPipeline}. - * - * @param start - * Start time for the key's validity. Null indicates immediate start. - * @param expiry - * Expiration of the key's validity. - * - * @return Emits the successful response. - * @throws IllegalArgumentException If {@code start} isn't null and is after {@code expiry}. - */ - public Mono getUserDelegationKey(OffsetDateTime start, OffsetDateTime expiry) { - Utility.assertNotNull("expiry", expiry); - if (start != null && !start.isBefore(expiry)) { - throw new IllegalArgumentException("`start` must be null or a datetime before `expiry`."); - } - - return postProcessResponse( - this.azureBlobStorage.services().getUserDelegationKeyWithRestResponseAsync( - new KeyInfo() - .start(start == null ? "" : Utility.ISO_8601_UTC_DATE_FORMATTER.format(start)) - .expiry(Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiry)), - null, null, Context.NONE) - ); - } - - /** - * Retrieves statistics related to replication for the Blob service. It is only available on the secondary - * location endpoint when read-access geo-redundant replication is enabled for the storage account. For more - * information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_stats "Sample code for ServiceURL.getStats")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getStatistics() { - return postProcessResponse( - this.azureBlobStorage.services().getStatisticsWithRestResponseAsync(null, null, Context.NONE)); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for ServiceURL.getAccountInfo")] \n - * For more samples, please see the [Samples file] (https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public Mono getAccountInfo() { - return postProcessResponse( - this.azureBlobStorage.services().getAccountInfoWithRestResponseAsync(Context.NONE)); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClient.java index c895cdda58bbc..1511726d653bb 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClient.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClient.java @@ -24,25 +24,24 @@ import java.time.OffsetDateTime; /** - * Client to a storage account. It may only be instantiated through a {@link StorageClientBuilder}. - * This class does not hold any state about a particular storage account but is - * instead a convenient way of sending off appropriate requests to the resource on the service. - * It may also be used to construct URLs to blobs and containers. + * Client to a storage account. It may only be instantiated through a {@link StorageClientBuilder}. This class does not + * hold any state about a particular storage account but is instead a convenient way of sending off appropriate requests + * to the resource on the service. It may also be used to construct URLs to blobs and containers. * *

- * This client contains operations on a blob. Operations on a container are available on {@link ContainerClient} - * through {@link #getContainerClient(String)}, and operations on a blob are available on {@link BlobClient}. + * This client contains operations on a blob. Operations on a container are available on {@link ContainerClient} through + * {@link #getContainerClient(String)}, and operations on a blob are available on {@link BlobClient}. * *

* Please see here for more * information on containers. */ public final class StorageClient { - - private StorageAsyncClient storageAsyncClient; + private final StorageAsyncClient storageAsyncClient; /** * Package-private constructor for use by {@link StorageClientBuilder}. + * * @param storageAsyncClient the async storage account client */ StorageClient(StorageAsyncClient storageAsyncClient) { @@ -53,10 +52,8 @@ public final class StorageClient { * Initializes a {@link ContainerClient} object pointing to the specified container. This method does not create a * container. It simply constructs the URL to the container and offers access to methods relevant to containers. * - * @param containerName - * The name of the container to point to. - * @return - * A {@link ContainerClient} object pointing to the specified container + * @param containerName The name of the container to point to. + * @return A {@link ContainerClient} object pointing to the specified container */ public ContainerClient getContainerClient(String containerName) { return new ContainerClient(storageAsyncClient.getContainerAsyncClient(containerName)); @@ -80,11 +77,9 @@ public Response createContainer(String containerName) { * Azure Docs. * * @param containerName Name of the container to create - * @param metadata - * {@link Metadata} - * @param accessType - * Specifies how the data in this container is available to the public. See the x-ms-blob-public-access header - * in the Azure Docs for more information. Pass null for no public access. + * @param metadata {@link Metadata} + * @param accessType Specifies how the data in this container is available to the public. See the + * x-ms-blob-public-access header in the Azure Docs for more information. Pass null for no public access. * @return A response containing a {@link ContainerClient} used to interact with the container created. */ public Response createContainer(String containerName, Metadata metadata, PublicAccessType accessType) { @@ -95,6 +90,7 @@ public Response createContainer(String containerName, Metadata /** * Gets the URL of the storage account represented by this client. + * * @return the URL. */ public URL getAccountUrl() { @@ -102,29 +98,24 @@ public URL getAccountUrl() { } /** - * Returns a lazy loaded list of containers in this account. The returned {@link Iterable} can be iterated - * through while new items are automatically retrieved as needed. For more information, see - * the Azure Docs. + * Returns a lazy loaded list of containers in this account. The returned {@link Iterable} can be iterated through + * while new items are automatically retrieved as needed. For more information, see the Azure Docs. * - * @return - * The list of containers. + * @return The list of containers. */ public Iterable listContainers() { return this.listContainers(new ListContainersOptions(), null); } /** - * Returns a lazy loaded list of containers in this account. The returned {@link Iterable} can be iterated - * through while new items are automatically retrieved as needed. For more information, see - * the Azure Docs. - * - * @param options - * A {@link ListContainersOptions} which specifies what data should be returned by the service. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * Returns a lazy loaded list of containers in this account. The returned {@link Iterable} can be iterated through + * while new items are automatically retrieved as needed. For more information, see the Azure Docs. * - * @return - * The list of containers. + * @param options A {@link ListContainersOptions} which specifies what data should be returned by the service. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The list of containers. */ public Iterable listContainers(ListContainersOptions options, Duration timeout) { Flux response = storageAsyncClient.listContainers(options); @@ -136,8 +127,7 @@ public Iterable listContainers(ListContainersOptions options, Dur * Gets the properties of a storage account’s Blob service. For more information, see the * Azure Docs. * - * @return - * The storage account properties. + * @return The storage account properties. */ public Response getProperties() { return this.getProperties(null); @@ -147,11 +137,8 @@ public Response getProperties() { * Gets the properties of a storage account’s Blob service. For more information, see the * Azure Docs. * - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The storage account properties. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The storage account properties. */ public Response getProperties(Duration timeout) { @@ -166,11 +153,8 @@ public Response getProperties(Duration timeout) { * Note that setting the default service version has no effect when using this client because this client explicitly * sets the version header on each request, overriding the default. * - * @param properties - * Configures the service. - * - * @return - * The storage account properties. + * @param properties Configures the service. + * @return The storage account properties. */ public VoidResponse setProperties(StorageServiceProperties properties) { return this.setProperties(properties, null); @@ -182,13 +166,9 @@ public VoidResponse setProperties(StorageServiceProperties properties) { * Note that setting the default service version has no effect when using this client because this client explicitly * sets the version header on each request, overriding the default. * - * @param properties - * Configures the service. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The storage account properties. + * @param properties Configures the service. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The storage account properties. */ public VoidResponse setProperties(StorageServiceProperties properties, Duration timeout) { Mono response = storageAsyncClient.setProperties(properties); @@ -197,66 +177,53 @@ public VoidResponse setProperties(StorageServiceProperties properties, Duration } /** - * Gets a user delegation key for use with this account's blob storage. - * Note: This method call is only valid when using {@link TokenCredential} in this object's {@link HttpPipeline}. + * Gets a user delegation key for use with this account's blob storage. Note: This method call is only valid when + * using {@link TokenCredential} in this object's {@link HttpPipeline}. * - * @param start - * Start time for the key's validity. Null indicates immediate start. - * @param expiry - * Expiration of the key's validity. - * - * @return - * The user delegation key. + * @param start Start time for the key's validity. Null indicates immediate start. + * @param expiry Expiration of the key's validity. + * @return The user delegation key. */ public Response getUserDelegationKey(OffsetDateTime start, OffsetDateTime expiry) { return this.getUserDelegationKey(start, expiry, null); } /** - * Gets a user delegation key for use with this account's blob storage. - * Note: This method call is only valid when using {@link TokenCredential} in this object's {@link HttpPipeline}. - * - * @param start - * Start time for the key's validity. Null indicates immediate start. - * @param expiry - * Expiration of the key's validity. - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. + * Gets a user delegation key for use with this account's blob storage. Note: This method call is only valid when + * using {@link TokenCredential} in this object's {@link HttpPipeline}. * - * @return - * The user delegation key. + * @param start Start time for the key's validity. Null indicates immediate start. + * @param expiry Expiration of the key's validity. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The user delegation key. */ public Response getUserDelegationKey(OffsetDateTime start, OffsetDateTime expiry, - Duration timeout) { + Duration timeout) { Mono> response = storageAsyncClient.getUserDelegationKey(start, expiry); return Utility.blockWithOptionalTimeout(response, timeout); } /** - * Retrieves statistics related to replication for the Blob service. It is only available on the secondary - * location endpoint when read-access geo-redundant replication is enabled for the storage account. For more - * information, see the + * Retrieves statistics related to replication for the Blob service. It is only available on the secondary location + * endpoint when read-access geo-redundant replication is enabled for the storage account. For more information, see + * the * Azure Docs. * - * @return - * The storage account statistics. + * @return The storage account statistics. */ public Response getStatistics() { return this.getStatistics(null); } /** - * Retrieves statistics related to replication for the Blob service. It is only available on the secondary - * location endpoint when read-access geo-redundant replication is enabled for the storage account. For more - * information, see the + * Retrieves statistics related to replication for the Blob service. It is only available on the secondary location + * endpoint when read-access geo-redundant replication is enabled for the storage account. For more information, see + * the * Azure Docs. * - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The storage account statistics. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The storage account statistics. */ public Response getStatistics(Duration timeout) { Mono> response = storageAsyncClient.getStatistics(); @@ -268,8 +235,7 @@ public Response getStatistics(Duration timeout) { * Returns the sku name and account kind for the account. For more information, please see the * Azure Docs. * - * @return - * The storage account info. + * @return The storage account info. */ public Response getAccountInfo() { return this.getAccountInfo(null); @@ -279,11 +245,8 @@ public Response getAccountInfo() { * Returns the sku name and account kind for the account. For more information, please see the * Azure Docs. * - * @param timeout - * An optional timeout value beyond which a {@link RuntimeException} will be raised. - * - * @return - * The storage account info. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @return The storage account info. */ public Response getAccountInfo(Duration timeout) { Mono> response = storageAsyncClient.getAccountInfo(); @@ -294,49 +257,33 @@ public Response getAccountInfo(Duration timeout) { /** * Generates an account SAS token with the specified parameters * - * @param accountSASService - * The {@code AccountSASService} services for the account SAS - * @param accountSASResourceType - * An optional {@code AccountSASResourceType} resources for the account SAS - * @param accountSASPermission - * The {@code AccountSASPermission} permission for the account SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the account SAS - * - * @return - * A string that represents the SAS token + * @param accountSASService The {@code AccountSASService} services for the account SAS + * @param accountSASResourceType An optional {@code AccountSASResourceType} resources for the account SAS + * @param accountSASPermission The {@code AccountSASPermission} permission for the account SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the account SAS + * @return A string that represents the SAS token */ public String generateAccountSAS(AccountSASService accountSASService, AccountSASResourceType accountSASResourceType, - AccountSASPermission accountSASPermission, OffsetDateTime expiryTime) { + AccountSASPermission accountSASPermission, OffsetDateTime expiryTime) { return this.storageAsyncClient.generateAccountSAS(accountSASService, accountSASResourceType, accountSASPermission, expiryTime); } /** * Generates an account SAS token with the specified parameters * - * @param accountSASService - * The {@code AccountSASService} services for the account SAS - * @param accountSASResourceType - * An optional {@code AccountSASResourceType} resources for the account SAS - * @param accountSASPermission - * The {@code AccountSASPermission} permission for the account SAS - * @param expiryTime - * The {@code OffsetDateTime} expiry time for the account SAS - * @param startTime - * The {@code OffsetDateTime} start time for the account SAS - * @param version - * The {@code String} version for the account SAS - * @param ipRange - * An optional {@code IPRange} ip address range for the SAS - * @param sasProtocol - * An optional {@code SASProtocol} protocol for the SAS - * - * @return - * A string that represents the SAS token + * @param accountSASService The {@code AccountSASService} services for the account SAS + * @param accountSASResourceType An optional {@code AccountSASResourceType} resources for the account SAS + * @param accountSASPermission The {@code AccountSASPermission} permission for the account SAS + * @param expiryTime The {@code OffsetDateTime} expiry time for the account SAS + * @param startTime The {@code OffsetDateTime} start time for the account SAS + * @param version The {@code String} version for the account SAS + * @param ipRange An optional {@code IPRange} ip address range for the SAS + * @param sasProtocol An optional {@code SASProtocol} protocol for the SAS + * @return A string that represents the SAS token */ public String generateAccountSAS(AccountSASService accountSASService, AccountSASResourceType accountSASResourceType, - AccountSASPermission accountSASPermission, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, IPRange ipRange, - SASProtocol sasProtocol) { + AccountSASPermission accountSASPermission, OffsetDateTime expiryTime, OffsetDateTime startTime, String version, IPRange ipRange, + SASProtocol sasProtocol) { return this.storageAsyncClient.generateAccountSAS(accountSASService, accountSASResourceType, accountSASPermission, expiryTime, startTime, version, ipRange, sasProtocol); } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageRawClient.java b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageRawClient.java deleted file mode 100644 index cebfdc451ae67..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageRawClient.java +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.http.HttpPipeline; -import com.azure.storage.blob.implementation.AzureBlobStorageImpl; -import com.azure.storage.blob.models.ListContainersOptions; -import com.azure.storage.blob.models.ServicesGetAccountInfoResponse; -import com.azure.storage.blob.models.ServicesGetPropertiesResponse; -import com.azure.storage.blob.models.ServicesGetStatisticsResponse; -import com.azure.storage.blob.models.ServicesGetUserDelegationKeyResponse; -import com.azure.storage.blob.models.ServicesListContainersSegmentResponse; -import com.azure.storage.blob.models.ServicesSetPropertiesResponse; -import com.azure.storage.blob.models.StorageServiceProperties; -import com.azure.storage.common.credentials.SASTokenCredential; -import reactor.core.publisher.Mono; - -import java.time.Duration; -import java.time.OffsetDateTime; - -/** - * Represents a URL to a storage service. This class does not hold any state about a particular storage account but is - * instead a convenient way of sending off appropriate requests to the resource on the service. - * It may also be used to construct URLs to blobs and containers. - * Please see here for more - * information on containers. - */ -final class StorageRawClient { - - StorageAsyncRawClient storageAsyncRawClient; - - /** - * Creates a {@code ServiceURL} object pointing to the account specified by the URL and using the provided pipeline - * to make HTTP requests. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_url "Sample code for ServiceURL constructor")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - StorageRawClient(AzureBlobStorageImpl azureBlobStorage) { - this.storageAsyncRawClient = new StorageAsyncRawClient(azureBlobStorage); - } - - /** - * Returns a Mono segment of containers starting from the specified Marker. - * Use an empty marker to start enumeration from the beginning. Container names are returned in lexicographic order. - * After getting a segment, process it, and then call ListContainers again (passing the the previously-returned - * Marker) to get the next segment. For more information, see - * the Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListContainersSegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param options - * A {@link ListContainersOptions} which specifies what data should be returned by the service. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list "Sample code for ServiceURL.listContainersSegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list_helper "Helper code for ServiceURL.listContainersSegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesListContainersSegmentResponse listContainersSegment(String marker, - ListContainersOptions options) { - return this.listContainersSegment(marker, options, null); - } - - /** - * Returns a Mono segment of containers starting from the specified Marker. - * Use an empty marker to start enumeration from the beginning. Container names are returned in lexicographic order. - * After getting a segment, process it, and then call ListContainers again (passing the the previously-returned - * Marker) to get the next segment. For more information, see - * the Azure Docs. - * - * @param marker - * Identifies the portion of the list to be returned with the next list operation. - * This value is returned in the response of a previous list operation as the - * ListContainersSegmentResponse.body().nextMarker(). Set to null to list the first segment. - * @param options - * A {@link ListContainersOptions} which specifies what data should be returned by the service. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list "Sample code for ServiceURL.listContainersSegment")] \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_list_helper "Helper code for ServiceURL.listContainersSegment")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesListContainersSegmentResponse listContainersSegment(String marker, - ListContainersOptions options, Duration timeout) { - Mono response = storageAsyncRawClient.listContainersSegment(marker, options); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Gets the properties of a storage account’s Blob service. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_getsetprops "Sample code for ServiceURL.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesGetPropertiesResponse getProperties() { - return this.getProperties(null); - } - - /** - * Gets the properties of a storage account’s Blob service. For more information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_getsetprops "Sample code for ServiceURL.getProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesGetPropertiesResponse getProperties(Duration timeout) { - Mono response = storageAsyncRawClient.getProperties(); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Sets properties for a storage account's Blob service endpoint. For more information, see the - * Azure Docs. - * Note that setting the default service version has no effect when using this client because this client explicitly - * sets the version header on each request, overriding the default. - * - * @param properties - * Configures the service. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_getsetprops "Sample code for ServiceURL.setProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesSetPropertiesResponse setProperties(StorageServiceProperties properties) { - return this.setProperties(properties, null); - } - - /** - * Sets properties for a storage account's Blob service endpoint. For more information, see the - * Azure Docs. - * Note that setting the default service version has no effect when using this client because this client explicitly - * sets the version header on each request, overriding the default. - * - * @param properties - * Configures the service. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_getsetprops "Sample code for ServiceURL.setProperties")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesSetPropertiesResponse setProperties(StorageServiceProperties properties, Duration timeout) { - Mono response = storageAsyncRawClient.setProperties(properties); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Gets a user delegation key for use with this account's blob storage. - * Note: This method call is only valid when using {@link SASTokenCredential} in this object's {@link HttpPipeline}. - * - * @param start - * Start time for the key's validity. Null indicates immediate start. - * @param expiry - * Expiration of the key's validity. - * - * @return Emits the successful response. - */ - public ServicesGetUserDelegationKeyResponse getUserDelegationKey(OffsetDateTime start, OffsetDateTime expiry) { - return this.getUserDelegationKey(start, expiry, null); - } - - /** - * Gets a user delegation key for use with this account's blob storage. - * Note: This method call is only valid when using {@link SASTokenCredential} in this object's {@link HttpPipeline}. - * - * @param start - * Start time for the key's validity. Null indicates immediate start. - * @param expiry - * Expiration of the key's validity. - * - * @return Emits the successful response. - */ - public ServicesGetUserDelegationKeyResponse getUserDelegationKey(OffsetDateTime start, OffsetDateTime expiry, - Duration timeout) { - Mono response = storageAsyncRawClient.getUserDelegationKey(start, expiry); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Retrieves statistics related to replication for the Blob service. It is only available on the secondary - * location endpoint when read-access geo-redundant replication is enabled for the storage account. For more - * information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_stats "Sample code for ServiceURL.getStats")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesGetStatisticsResponse getStatistics() { - return this.getStatistics(null); - } - - /** - * Retrieves statistics related to replication for the Blob service. It is only available on the secondary - * location endpoint when read-access geo-redundant replication is enabled for the storage account. For more - * information, see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_stats "Sample code for ServiceURL.getStats")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesGetStatisticsResponse getStatistics(Duration timeout) { - Mono response = storageAsyncRawClient.getStatistics(); - return Utility.blockWithOptionalTimeout(response, timeout); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for ServiceURL.getAccountInfo")] \n - * For more samples, please see the [Samples file] (https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesGetAccountInfoResponse getAccountInfo() { - return this.getAccountInfo(null); - } - - /** - * Returns the sku name and account kind for the account. For more information, please see the - * Azure Docs. - * - * @return Emits the successful response. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_info "Sample code for ServiceURL.getAccountInfo")] \n - * For more samples, please see the [Samples file] (https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) - */ - public ServicesGetAccountInfoResponse getAccountInfo(Duration timeout) { - Mono response = storageAsyncRawClient.getAccountInfo(); - return Utility.blockWithOptionalTimeout(response, timeout); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/common/credentials/SASTokenCredential.java b/storage/client/blob/src/main/java/com/azure/storage/common/credentials/SASTokenCredential.java index 4dce1c831ce21..cff7bc91f7765 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/common/credentials/SASTokenCredential.java +++ b/storage/client/blob/src/main/java/com/azure/storage/common/credentials/SASTokenCredential.java @@ -43,6 +43,7 @@ public final class SASTokenCredential { /** * Creates a SAS token credential from the passed SAS token. + * * @param sasToken SAS token used to authenticate requests with the service. */ public SASTokenCredential(String sasToken) { @@ -58,6 +59,7 @@ public String sasToken() { /** * Creates a SAS token credential from the passed URL query string + * * @param query URL query used to build the SAS token * @return a SAS token credential if the query param contains all the necessary pieces */ diff --git a/storage/client/blob/src/main/java/com/azure/storage/common/policy/SharedKeyCredentialPolicy.java b/storage/client/blob/src/main/java/com/azure/storage/common/policy/SharedKeyCredentialPolicy.java index 743eae8f262e4..e8b54914cb762 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/common/policy/SharedKeyCredentialPolicy.java +++ b/storage/client/blob/src/main/java/com/azure/storage/common/policy/SharedKeyCredentialPolicy.java @@ -18,6 +18,7 @@ public final class SharedKeyCredentialPolicy implements HttpPipelinePolicy { /** * Creates a SharedKey pipeline policy that adds the SharedKey into the request's authorization header. + * * @param credential the SharedKey credential used to create the policy. */ public SharedKeyCredentialPolicy(SharedKeyCredential credential) { @@ -25,9 +26,7 @@ public SharedKeyCredentialPolicy(SharedKeyCredential credential) { } /** - * Gets the shared key credential linked to the policy. - * @return - * The {@link SharedKeyCredential} linked to the policy. + * @return the {@link SharedKeyCredential} linked to the policy. */ public SharedKeyCredential sharedKeyCredential() { return this.credential; diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/Sample.java b/storage/client/blob/src/test/java/com/azure/storage/blob/Sample.java index 1ac12cbe978da..f6d19d6b84167 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/Sample.java +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/Sample.java @@ -8,6 +8,8 @@ import com.azure.storage.blob.models.BlobItem; import com.azure.storage.blob.models.ContainerItem; import com.azure.storage.common.credentials.SharedKeyCredential; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -16,7 +18,6 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.UUID; @@ -116,8 +117,7 @@ public void asyncSample() throws IOException { for (int i = 0; i < 5; i++) { BlockBlobAsyncClient blobClient = finalContainerClient.getBlockBlobAsyncClient("testblob-" + i); byte[] message = ("test data" + i).getBytes(StandardCharsets.UTF_8); - Flux testdata = Flux.just(ByteBuffer.wrap(message)); - + Flux testdata = Flux.just(ByteBufAllocator.DEFAULT.buffer(message.length).writeBytes(message)); finished = finished.and(blobClient.upload(testdata, message.length) .then(Mono.defer(() -> { From 5dbcbacc65786966e37afa6973e1aa009cb68364 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Wed, 17 Jul 2019 15:09:34 -0700 Subject: [PATCH 11/19] DownloadResponseTest working --- .../azure/storage/blob/DownloadResponse.java | 4 +-- .../blob/DownloadResponseMockFlux.java | 28 +++++++-------- .../storage/blob/DownloadResponseTest.groovy | 34 +++++-------------- 3 files changed, 24 insertions(+), 42 deletions(-) diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadResponse.java b/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadResponse.java index 5ea997a72b34d..32869259bd337 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadResponse.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/DownloadResponse.java @@ -102,9 +102,9 @@ private Flux applyReliableDownload(Flux data, int currentRetry Update how much data we have received in case we need to retry and propagate to the user the data we have received. */ - this.info.offset(this.info.offset() + buffer.readableBytes()); // was `remaining()` in Rx world + this.info.offset(this.info.offset() + buffer.readableBytes()); if (this.info.count() != null) { - this.info.count(this.info.count() - buffer.readableBytes()); // was `remaining()` in Rx world + this.info.count(this.info.count() - buffer.readableBytes()); } }).onErrorResume(t2 -> { // Increment the retry count and try again with the new exception. diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/DownloadResponseMockFlux.java b/storage/client/blob/src/test/java/com/azure/storage/blob/DownloadResponseMockFlux.java index b5539e57a3c0d..85df7c38a355f 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/DownloadResponseMockFlux.java +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/DownloadResponseMockFlux.java @@ -13,6 +13,7 @@ import reactor.core.CoreSubscriber; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.core.publisher.Operators; import java.io.IOException; import java.nio.ByteBuffer; @@ -39,7 +40,6 @@ public DownloadResponseMockFlux(int scenario) { this.scenarioData = APISpec.getRandomData(512 * 1024); break; case DR_TEST_SCENARIO_SUCCESSFUL_MULTI_CHUNK: - // Fall through case DR_TEST_SCENARIO_SUCCESSFUL_STREAM_FAILURES: this.scenarioData = APISpec.getRandomData(1024); break; @@ -66,7 +66,7 @@ public void subscribe(CoreSubscriber subscriber) { switch (this.scenario) { case DR_TEST_SCENARIO_SUCCESSFUL_ONE_CHUNK: subscriber.onNext(Unpooled.wrappedBuffer(this.scenarioData.duplicate())); - subscriber.onComplete(); + Operators.complete(subscriber); break; case DR_TEST_SCENARIO_SUCCESSFUL_MULTI_CHUNK: @@ -76,7 +76,7 @@ public void subscribe(CoreSubscriber subscriber) { toSend.limit((i + 1) * 256); subscriber.onNext(Unpooled.wrappedBuffer(toSend)); } - subscriber.onComplete(); + Operators.complete(subscriber); break; case DR_TEST_SCENARIO_SUCCESSFUL_STREAM_FAILURES: @@ -84,34 +84,34 @@ public void subscribe(CoreSubscriber subscriber) { // tryNumber is 1 indexed, so we have to sub 1. if (this.info.offset() != (this.tryNumber - 1) * 256 || this.info.count() != this.scenarioData.remaining() - (this.tryNumber - 1) * 256) { - subscriber.onError(new IllegalArgumentException("Info values are incorrect.")); + Operators.error(subscriber, new IllegalArgumentException("Info values are incorrect.")); return; } ByteBuffer toSend = this.scenarioData.duplicate(); toSend.position((this.tryNumber - 1) * 256); toSend.limit(this.tryNumber * 256); subscriber.onNext(Unpooled.wrappedBuffer(toSend)); - subscriber.onError(new IOException()); + Operators.error(subscriber, new IOException()); break; } if (this.info.offset() != (this.tryNumber - 1) * 256 || this.info.count() != this.scenarioData.remaining() - (this.tryNumber - 1) * 256) { - subscriber.onError(new IllegalArgumentException("Info values are incorrect.")); + Operators.error(subscriber, new IllegalArgumentException("Info values are incorrect.")); return; } ByteBuffer toSend = this.scenarioData.duplicate(); toSend.position((this.tryNumber - 1) * 256); toSend.limit(this.tryNumber * 256); subscriber.onNext(Unpooled.wrappedBuffer(toSend)); - subscriber.onComplete(); + Operators.complete(subscriber); break; case DR_TEST_SCENARIO_MAX_RETRIES_EXCEEDED: - subscriber.onError(new IOException()); + Operators.error(subscriber, new IOException()); break; case DR_TEST_SCENARIO_NON_RETRYABLE_ERROR: - subscriber.onError(new Exception()); + Operators.error(subscriber, new Exception()); break; case DR_TEST_SCENARIO_ERROR_GETTER_MIDDLE: @@ -120,9 +120,9 @@ public void subscribe(CoreSubscriber subscriber) { We return a retryable error here so we have to invoke the getter, which will throw an error in this case. */ - subscriber.onError(new IOException()); + Operators.error(subscriber, new IOException()); } else { - subscriber.onError(new IllegalArgumentException("Retried after getter error.")); + Operators.error(subscriber, new IllegalArgumentException("Retried after getter error.")); } break; @@ -130,11 +130,11 @@ public void subscribe(CoreSubscriber subscriber) { switch (this.tryNumber) { case 1: // Test the value of info when getting the initial response. case 2: // Test the value of info when getting an intermediate response. - subscriber.onError(new IOException()); + Operators.error(subscriber, new IOException()); break; case 3: // All calls to getter checked. Exit. This test does not check for data. - subscriber.onComplete(); + Operators.complete(subscriber); break; default: throw new IllegalArgumentException("Invalid try number."); @@ -142,7 +142,7 @@ public void subscribe(CoreSubscriber subscriber) { break; default: - subscriber.onError(new IllegalArgumentException("Invalid test case")); + Operators.error(subscriber, new IllegalArgumentException("Invalid test case")); } } diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/DownloadResponseTest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/DownloadResponseTest.groovy index 2834bccd3bd48..71b2547875d02 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/DownloadResponseTest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/DownloadResponseTest.groovy @@ -3,14 +3,10 @@ package com.azure.storage.blob -import com.azure.core.http.rest.ResponseBase + import com.azure.core.implementation.util.FluxUtil -import com.azure.storage.blob.models.BlobDownloadHeaders import com.azure.storage.blob.models.ReliableDownloadOptions import com.azure.storage.blob.models.StorageErrorException -import io.netty.buffer.ByteBuf -import io.netty.buffer.Unpooled -import reactor.core.publisher.Flux import spock.lang.Unroll class DownloadResponseTest extends APISpec { @@ -25,6 +21,7 @@ class DownloadResponseTest extends APISpec { This shouldn't really be different from anything else we're doing in the other tests. Just a sanity check against a real use case. */ + def "Network call"() { expect: OutputStream outputStream = new ByteArrayOutputStream() @@ -44,13 +41,11 @@ class DownloadResponseTest extends APISpec { ReliableDownloadOptions options = new ReliableDownloadOptions().maxRetryRequests(5) - ResponseBase> mockRawResponse = flux.getter(info).block().rawResponse() - when: - DownloadResponse response = new DownloadResponse(mockRawResponse, info, { HTTPGetterInfo newInfo -> flux.getter(newInfo) }) + DownloadResponse response = flux.getter(info).block() then: - FluxUtil.collectByteBufStream(response.body(options), false).block() == Unpooled.wrappedBuffer(flux.getScenarioData()) + FluxUtil.collectByteBufStream(response.body(options), false).block().nioBuffer() == flux.getScenarioData() flux.getTryNumber() == tryNumber @@ -65,17 +60,11 @@ class DownloadResponseTest extends APISpec { def "Failure"() { setup: DownloadResponseMockFlux flux = new DownloadResponseMockFlux(scenario) - - ReliableDownloadOptions options = new ReliableDownloadOptions() - .maxRetryRequests(5) - + ReliableDownloadOptions options = new ReliableDownloadOptions().maxRetryRequests(5) HTTPGetterInfo info = new HTTPGetterInfo().eTag("etag") - ResponseBase> mockRawResponse = flux.getter(info).block().rawResponse() when: - DownloadResponse response = new DownloadResponse(mockRawResponse, info, { HTTPGetterInfo newInfo -> - flux.getter(newInfo) - }) + DownloadResponse response = flux.getter(info).block() response.body(options).blockFirst() then: @@ -103,11 +92,7 @@ class DownloadResponseTest extends APISpec { DownloadResponseMockFlux flux = new DownloadResponseMockFlux(DownloadResponseMockFlux.DR_TEST_SCENARIO_SUCCESSFUL_ONE_CHUNK) when: - new DownloadResponse(flux.getter(info).block().rawResponse(), info, - { HTTPGetterInfo newInfo -> - flux.getter(newInfo) - }) - + new DownloadResponse(flux.getter(info).block().rawResponse(), info, { HTTPGetterInfo newInfo -> flux.getter(newInfo) }) then: thrown(IllegalArgumentException) @@ -150,10 +135,7 @@ class DownloadResponseTest extends APISpec { ReliableDownloadOptions options = new ReliableDownloadOptions().maxRetryRequests(5) when: - def response = new DownloadResponse(flux.getter(info).block().rawResponse(), info, - { HTTPGetterInfo newInfo -> - return flux.getter(newInfo) - }) + DownloadResponse response = flux.getter(info).block() response.body(options).blockFirst() then: From f0dddf509bf3508fa82b61767f4c89ce2ecd9cb2 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Thu, 18 Jul 2019 16:45:50 -0700 Subject: [PATCH 12/19] Fixed scoping for some classes, Checkstyle fixes --- .../storage/blob/AccountSASPermission.java | 63 +++++--- .../storage/blob/AccountSASResourceType.java | 25 ++- .../azure/storage/blob/AccountSASService.java | 30 ++-- .../blob/AccountSASSignatureValues.java | 77 ++++++---- .../blob/AnonymousCredentialPolicy.java | 32 ---- .../storage/blob/AppendBlobClientBuilder.java | 2 - .../azure/storage/blob/BlobClientBuilder.java | 2 - .../azure/storage/blob/BlobConfiguration.java | 4 +- .../azure/storage/blob/BlobOutputStream.java | 2 +- .../azure/storage/blob/BlobProperties.java | 14 +- .../azure/storage/blob/BlobSASPermission.java | 37 +++-- .../storage/blob/BlockBlobClientBuilder.java | 2 - .../storage/blob/ContainerClientBuilder.java | 2 - .../storage/blob/ContainerProperties.java | 20 ++- .../storage/blob/ContainerSASPermission.java | 46 ++++-- .../azure/storage/blob/HTTPGetterInfo.java | 35 +++-- .../java/com/azure/storage/blob/IPRange.java | 19 ++- .../azure/storage/blob/IProgressReceiver.java | 10 +- .../storage/blob/PageBlobClientBuilder.java | 2 - .../azure/storage/blob/ProgressReporter.java | 69 ++++----- .../com/azure/storage/blob/SASProtocol.java | 2 +- .../storage/blob/SASQueryParameters.java | 96 ++++++------ .../blob/ServiceSASSignatureValues.java | 144 ++++++++++++------ .../storage/blob/StorageClientBuilder.java | 2 - .../blob/UnexpectedLengthException.java | 16 +- .../java/com/azure/storage/blob/Utility.java | 3 +- .../storage/blob/ProgressReporterTest.groovy | 19 ++- .../com/azure/storage/blob/SASTest.groovy | 71 ++++----- 28 files changed, 501 insertions(+), 345 deletions(-) delete mode 100644 storage/client/blob/src/main/java/com/azure/storage/blob/AnonymousCredentialPolicy.java diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASPermission.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASPermission.java index 87ef5998c7252..4af9f970ff647 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASPermission.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASPermission.java @@ -12,7 +12,7 @@ * {@link AccountSASSignatureValues} object. It is possible to construct the permissions string without this class, but * the order of the permissions is particular and this class guarantees correctness. */ -final class AccountSASPermission { +public final class AccountSASPermission { private boolean read; @@ -85,14 +85,17 @@ public static AccountSASPermission parse(String permString) { } /** - * Permission to read resources and list queues and tables granted. + * @return the read permission status */ public boolean read() { return read; } /** - * Permission to read resources and list queues and tables granted. + * Sets the read permission status. + * + * @param read Permission status to set + * @return the updated AccountSASPermission object */ public AccountSASPermission read(boolean read) { this.read = read; @@ -100,14 +103,17 @@ public AccountSASPermission read(boolean read) { } /** - * Permission to add messages, table entities, and append to blobs granted. + * @return the add permission status */ public boolean add() { return add; } /** - * Permission to add messages, table entities, and append to blobs granted. + * Sets the add permission status. + * + * @param add Permission status to set + * @return the updated AccountSASPermission object */ public AccountSASPermission add(boolean add) { this.add = add; @@ -115,14 +121,17 @@ public AccountSASPermission add(boolean add) { } /** - * Permission to create blobs and files granted. + * @return the create permission status */ public boolean create() { return create; } /** - * Permission to create blobs and files granted. + * Sets the create permission status. + * + * @param create Permission status to set + * @return the updated AccountSASPermission object */ public AccountSASPermission create(boolean create) { this.create = create; @@ -130,14 +139,17 @@ public AccountSASPermission create(boolean create) { } /** - * Permission to write resources granted. + * @return the write permission status */ public boolean write() { return write; } /** - * Permission to write resources granted. + * Sets the write permission status. + * + * @param write Permission status to set + * @return the updated AccountSASPermission object */ public AccountSASPermission write(boolean write) { this.write = write; @@ -145,14 +157,17 @@ public AccountSASPermission write(boolean write) { } /** - * Permission to delete resources granted. + * @return the delete permission status */ public boolean delete() { return delete; } /** - * Permission to delete resources granted. + * Sets the delete permission status. + * + * @param delete Permission status to set + * @return the updated AccountSASPermission object */ public AccountSASPermission delete(boolean delete) { this.delete = delete; @@ -160,14 +175,18 @@ public AccountSASPermission delete(boolean delete) { } /** - * Permission to list blob containers, blobs, shares, directories, and files granted. + * @return the list permission status */ public boolean list() { return list; } /** - * Permission to list blob containers, blobs, shares, directories, and files granted. + * Sets the list permission status. This permission grants the ability to list blob containers, blobs, shares, + * directories, and files. + * + * @param list Permission status to set + * @return the updated AccountSASPermission object */ public AccountSASPermission list(boolean list) { this.list = list; @@ -175,14 +194,19 @@ public AccountSASPermission list(boolean list) { } /** - * Permissions to update messages and table entities granted. + * Returns the update permission status, it allows the update of queue message and tables. + * + * @return the update permission status */ public boolean update() { return update; } /** - * Permissions to update messages and table entities granted. + * Sets the update permission status, it allows the update of queue messages and tables. + * + * @param update Permission status to set + * @return the updated AccountSASPermission object */ public AccountSASPermission update(boolean update) { this.update = update; @@ -190,14 +214,19 @@ public AccountSASPermission update(boolean update) { } /** - * Permission to get and delete messages granted. + * Returns the process messages permission, this allows the retrieval and deletion of queue messages. + * + * @return the process messages permission status. */ public boolean processMessages() { return processMessages; } /** - * Permission to get and delete messages granted. + * Sets the process messages permission, this allows the retrieval and deletion of queue messages. + * + * @param processMessages Permission status to set + * @return the updated AccountSASPermission object */ public AccountSASPermission processMessages(boolean processMessages) { this.processMessages = processMessages; diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASResourceType.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASResourceType.java index a1b47dc4f7372..a91abf9175011 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASResourceType.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASResourceType.java @@ -12,7 +12,7 @@ * {@link AccountSASSignatureValues} object. It is possible to construct the resources string without this class, but * the order of the resources is particular and this class guarantees correctness. */ -final class AccountSASResourceType { +public final class AccountSASResourceType { private boolean service; @@ -61,14 +61,17 @@ public static AccountSASResourceType parse(String resourceTypesString) { } /** - * Permission to access service level APIs granted. + * @return the access status for service level APIs. */ public boolean service() { return service; } /** - * Permission to access service level APIs granted. + * Sets the access status for service level APIs. + * + * @param service Access status to set + * @return the updated AccountSASResourceType object. */ public AccountSASResourceType service(boolean service) { this.service = service; @@ -76,14 +79,18 @@ public AccountSASResourceType service(boolean service) { } /** - * Permission to access container level APIs (Blob Containers, Tables, Queues, File Shares) granted. + * @return the access status for container level APIs, this grants access to Blob Containers, Tables, Queues, + * and File Shares. */ public boolean container() { return container; } /** - * Permission to access container level APIs (Blob Containers, Tables, Queues, File Shares) granted. + * Sets the access status for container level APIs, this grants access to Blob Containers, Tables, Queues, and File Shares. + * + * @param container Access status to set + * @return the updated AccountSASResourceType object. */ public AccountSASResourceType container(boolean container) { this.container = container; @@ -91,14 +98,18 @@ public AccountSASResourceType container(boolean container) { } /** - * Permission to access object level APIs (Blobs, Table Entities, Queue Messages, Files) granted. + * @return the access status for object level APIs, this grants access to Blobs, Table Entities, Queue Messages, + * Files. */ public boolean object() { return object; } /** - * Permission to access object level APIs (Blobs, Table Entities, Queue Messages, Files) granted. + * Sets the access status for object level APIs, this grants access to Blobs, Table Entities, Queue Messages, Files. + * + * @param object Access status to set + * @return the updated AccountSASResourceType object. */ public AccountSASResourceType object(boolean object) { this.object = object; diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASService.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASService.java index 359085fb13084..9fbb9137bcbd6 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASService.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASService.java @@ -12,7 +12,7 @@ * {@link AccountSASSignatureValues} object. It is possible to construct the services string without this class, but * the order of the services is particular and this class guarantees correctness. */ -final class AccountSASService { +public final class AccountSASService { private boolean blob; @@ -66,14 +66,17 @@ public static AccountSASService parse(String servicesString) { } /** - * Permission to access blob resources granted. + * @return the access status for blob resources. */ public boolean blob() { return blob; } /** - * Permission to access blob resources granted. + * Sets the access status for blob resources. + * + * @param blob Access status to set + * @return the updated AccountSASService object. */ public AccountSASService blob(boolean blob) { this.blob = blob; @@ -81,14 +84,17 @@ public AccountSASService blob(boolean blob) { } /** - * Permission to access file resources granted. + * @return the access status for file resources. */ public boolean file() { return file; } /** - * Permission to access file resources granted. + * Sets the access status for file resources. + * + * @param file Access status to set + * @return the updated AccountSASService object. */ public AccountSASService file(boolean file) { this.file = file; @@ -96,14 +102,17 @@ public AccountSASService file(boolean file) { } /** - * Permission to access queue resources granted. + * @return the access status for queue resources. */ public boolean queue() { return queue; } /** - * Permission to access queue resources granted. + * Sets the access status for queue resources. + * + * @param queue Access status to set + * @return the updated AccountSASService object. */ public AccountSASService queue(boolean queue) { this.queue = queue; @@ -111,14 +120,17 @@ public AccountSASService queue(boolean queue) { } /** - * Permission to access table resources granted. + * @return the access status for table resources. */ public boolean table() { return table; } /** - * Permission to access table resources granted. + * Sets the access status for table resources. + * + * @param table Access status to set + * @return the updated AccountSASService object. */ public AccountSASService table(boolean table) { this.table = table; diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASSignatureValues.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASSignatureValues.java index c8c3e0e58a78b..beb4817a5470c 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASSignatureValues.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AccountSASSignatureValues.java @@ -24,11 +24,11 @@ * here for further * descriptions of the parameters, including which are required: * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=account_sas "Sample code for AccountSASSignatureValues")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) + *

Please see + * here + * for additional samples.

*/ -final class AccountSASSignatureValues { +public final class AccountSASSignatureValues { private String version = Constants.HeaderConstants.TARGET_STORAGE_VERSION; @@ -54,14 +54,19 @@ public AccountSASSignatureValues() { } /** - * If null or empty, this defaults to the service version targeted by this version of the library. + * @return the service version that is targeted, if {@code null} or empty the service version targeted by the + * library will be used. */ public String version() { return version; } /** - * If null or empty, this defaults to the service version targeted by this version of the library. + * Sets the service version that is targeted. Leave this {@code null} or empty to target the version used by the + * library. + * + * @param version Target version to set + * @return the updated AccountSASSignatureValues object. */ public AccountSASSignatureValues version(String version) { this.version = version; @@ -69,14 +74,17 @@ public AccountSASSignatureValues version(String version) { } /** - * {@link SASProtocol} + * @return the {@link SASProtocol} which determines the HTTP protocol that will be used. */ public SASProtocol protocol() { return protocol; } /** - * {@link SASProtocol} + * Sets the {@link SASProtocol} which determines the HTTP protocol that will be used. + * + * @param protocol Protocol to set + * @return the updated AccountSASSignatureValues object. */ public AccountSASSignatureValues protocol(SASProtocol protocol) { this.protocol = protocol; @@ -84,14 +92,17 @@ public AccountSASSignatureValues protocol(SASProtocol protocol) { } /** - * When the SAS will take effect. + * @return when the SAS will take effect. */ public OffsetDateTime startTime() { return startTime; } /** - * When the SAS will take effect. + * Sets when the SAS will take effect. + * + * @param startTime Start time to set + * @return the updated AccountSASSignatureValues object. */ public AccountSASSignatureValues startTime(OffsetDateTime startTime) { this.startTime = startTime; @@ -99,14 +110,17 @@ public AccountSASSignatureValues startTime(OffsetDateTime startTime) { } /** - * The time after which the SAS will no longer work. + * @return the time after which the SAS will no longer work. */ public OffsetDateTime expiryTime() { return expiryTime; } /** - * The time after which the SAS will no longer work. + * Sets the time after which the SAS will no longer work. + * + * @param expiryTime Expiry time to set + * @return the updated AccountSASSignatureValues object. */ public AccountSASSignatureValues expiryTime(OffsetDateTime expiryTime) { this.expiryTime = expiryTime; @@ -114,16 +128,19 @@ public AccountSASSignatureValues expiryTime(OffsetDateTime expiryTime) { } /** - * Specifies which operations the SAS user may perform. Please refer to {@link AccountSASPermission} for help - * constructing the permissions string. + * @return the operations the SAS user may perform. Please refer to {@link AccountSASPermission} to help determine + * which permissions are allowed. */ public String permissions() { return permissions; } /** - * Specifies which operations the SAS user may perform. Please refer to {@link AccountSASPermission} for help - * constructing the permissions string. + * Sets the operations the SAS user may perform. Please refer to {@link AccountSASPermission} for help constructing + * the permissions string. + * + * @param permissions Permissions string to set + * @return the updated AccountSASSignatureValues object. */ public AccountSASSignatureValues permissions(String permissions) { this.permissions = permissions; @@ -131,14 +148,17 @@ public AccountSASSignatureValues permissions(String permissions) { } /** - * {@link IPRange} + * @return the {@link IPRange} which determines the IP ranges that are allowed to use the SAS. */ public IPRange ipRange() { return ipRange; } /** - * {@link IPRange} + * Sets the {@link IPRange} which determines the IP ranges that are allowed to use the SAS. + * + * @param ipRange Allowed IP range to set + * @return the updated AccountSASSignatureValues object. */ public AccountSASSignatureValues ipRange(IPRange ipRange) { this.ipRange = ipRange; @@ -146,16 +166,18 @@ public AccountSASSignatureValues ipRange(IPRange ipRange) { } /** - * The values that indicate the services accessible with this SAS. Please refer to {@link AccountSASService} to - * construct this value. + * @return the services accessible with this SAS. Please refer to {@link AccountSASService} to help determine which + * services are accessible. */ public String services() { return services; } /** - * The values that indicate the services accessible with this SAS. Please refer to {@link AccountSASService} to - * construct this value. + * Sets the services accessible with this SAS. Please refer to {@link AccountSASService} to construct this value. + * + * @param services Allowed services string to set + * @return the updated AccountSASSignatureValues object. */ public AccountSASSignatureValues services(String services) { this.services = services; @@ -163,16 +185,19 @@ public AccountSASSignatureValues services(String services) { } /** - * The values that indicate the resource types accessible with this SAS. Please refer - * to {@link AccountSASResourceType} to construct this value. + * @return the resource types accessible with this SAS. Please refer to {@link AccountSASResourceType} to help + * determine the resource types that are accessible. */ public String resourceTypes() { return resourceTypes; } /** - * The values that indicate the resource types accessible with this SAS. Please refer - * to {@link AccountSASResourceType} to construct this value. + * Sets the resource types accessible with this SAS. Please refer to {@link AccountSASResourceType} to construct + * this value. + * + * @param resourceTypes Allowed resource types string to set + * @return the updated AccountSASSignatureValues object. */ public AccountSASSignatureValues resourceTypes(String resourceTypes) { this.resourceTypes = resourceTypes; diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AnonymousCredentialPolicy.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AnonymousCredentialPolicy.java deleted file mode 100644 index d54b264702f6b..0000000000000 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AnonymousCredentialPolicy.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.storage.blob; - -import com.azure.core.http.HttpPipelineCallContext; -import com.azure.core.http.HttpPipelineNextPolicy; -import com.azure.core.http.HttpResponse; -import com.azure.core.http.policy.HttpPipelinePolicy; -import reactor.core.publisher.Mono; - -/** - * Anonymous credentials are to be used with with HTTP(S) requests that read blobs from public containers or requests - * that use a Shared Access Signature (SAS). This is because Anonymous credentials will not set an Authorization header. - * Pass an instance of this class as the credentials parameter when creating a new pipeline (typically with - * {@link StorageClient}). - */ -public final class AnonymousCredentialPolicy implements HttpPipelinePolicy { - - /** - * Returns an empty instance of {@code AnonymousCredentials}. - */ - public AnonymousCredentialPolicy() { - } - - - - @Override - public Mono process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) { - return next.process(); - } -} diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobClientBuilder.java b/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobClientBuilder.java index c46883e7fecf9..2d88a971d9b31 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobClientBuilder.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/AppendBlobClientBuilder.java @@ -100,8 +100,6 @@ private AzureBlobStorageBuilder buildImpl() { policies.add(new BearerTokenAuthenticationPolicy(tokenCredential, String.format("%s/.default", endpoint))); } else if (sasTokenCredential != null) { policies.add(new SASTokenCredentialPolicy(sasTokenCredential)); - } else { - policies.add(new AnonymousCredentialPolicy()); } policies.add(new RequestRetryPolicy(retryOptions)); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClientBuilder.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClientBuilder.java index 2238af18e48e9..5c009739f7775 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClientBuilder.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobClientBuilder.java @@ -100,8 +100,6 @@ private AzureBlobStorageBuilder buildImpl() { policies.add(new BearerTokenAuthenticationPolicy(tokenCredential, String.format("%s/.default", endpoint))); } else if (sasTokenCredential != null) { policies.add(new SASTokenCredentialPolicy(sasTokenCredential)); - } else { - policies.add(new AnonymousCredentialPolicy()); } policies.add(new RequestRetryPolicy(retryOptions)); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobConfiguration.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobConfiguration.java index ae1e422a24c0a..74b069df35e87 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobConfiguration.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobConfiguration.java @@ -3,6 +3,6 @@ package com.azure.storage.blob; class BlobConfiguration { - static final String NAME = "storage-blob"; - static final String VERSION = "1.0.0-SNAPSHOT"; + static final String NAME = "azure-storage-blob"; + static final String VERSION = "12.0.0-preview.1"; } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobOutputStream.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobOutputStream.java index c733a580f2500..668f7a2c20663 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobOutputStream.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobOutputStream.java @@ -30,7 +30,7 @@ import java.util.concurrent.atomic.AtomicLongFieldUpdater; -public class BlobOutputStream extends OutputStream { +public final class BlobOutputStream extends OutputStream { /** * Holds the {@link BlobAccessConditions} object that represents the access conditions for the blob. */ diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobProperties.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobProperties.java index 321a7c061f387..307ebbc140e40 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobProperties.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobProperties.java @@ -8,7 +8,9 @@ import com.azure.storage.blob.models.BlobType; import com.azure.storage.blob.models.Metadata; -public class BlobProperties { +import java.time.OffsetDateTime; + +public final class BlobProperties { private final BlobType blobType; @@ -26,7 +28,7 @@ public class BlobProperties { private final String cacheControl; - //todo decide datetime representation for last modified time + private final OffsetDateTime lastModified; BlobProperties(BlobGetPropertiesHeaders generatedHeaders) { @@ -38,6 +40,7 @@ public class BlobProperties { this.contentDisposition = generatedHeaders.contentDisposition(); this.contentLanguage = generatedHeaders.contentLanguage(); this.cacheControl = generatedHeaders.cacheControl(); + this.lastModified = generatedHeaders.lastModified(); } @@ -96,4 +99,11 @@ public String contentLanguage() { public String cacheControl() { return cacheControl; } + + /** + * @return the time this blob was last modified + */ + public OffsetDateTime lastModified() { + return lastModified; + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobSASPermission.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobSASPermission.java index 6c8843a140bd8..24f4b0d0a96c4 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlobSASPermission.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlobSASPermission.java @@ -12,7 +12,7 @@ * {@link ServiceSASSignatureValues} object. It is possible to construct the permissions string without this class, but * the order of the permissions is particular and this class guarantees correctness. */ -final class BlobSASPermission { +public final class BlobSASPermission { private boolean read; @@ -70,14 +70,17 @@ public static BlobSASPermission parse(String permString) { } /** - * Specifies Read access granted. + * @return the read permission status. */ public boolean read() { return read; } /** - * Specifies Read access granted. + * Sets the read permission status. + * + * @param read Permission status to set + * @return the updated BlobSASPermission object. */ public BlobSASPermission read(boolean read) { this.read = read; @@ -85,14 +88,17 @@ public BlobSASPermission read(boolean read) { } /** - * Specifies Add access granted. + * @return the add permission status. */ public boolean add() { return add; } /** - * Specifies Add access granted. + * Sets the add permission status. + * + * @param add Permission status to set + * @return the updated BlobSASPermission object. */ public BlobSASPermission add(boolean add) { this.add = add; @@ -100,14 +106,17 @@ public BlobSASPermission add(boolean add) { } /** - * Specifies Create access granted. + * @return the create permission status. */ public boolean create() { return create; } /** - * Specifies Create access granted. + * Sets the create permission status. + * + * @param create Permission status to set + * @return the updated BlobSASPermission object. */ public BlobSASPermission create(boolean create) { this.create = create; @@ -115,14 +124,17 @@ public BlobSASPermission create(boolean create) { } /** - * Specifies Write access granted. + * @return the write permission status. */ public boolean write() { return write; } /** - * Specifies Write access granted. + * Sets the write permission status. + * + * @param write Permission status to set + * @return the updated BlobSASPermission object. */ public BlobSASPermission write(boolean write) { this.write = write; @@ -130,14 +142,17 @@ public BlobSASPermission write(boolean write) { } /** - * Specifies Delete access granted. + * @return the delete permission status. */ public boolean delete() { return delete; } /** - * Specifies Delete access granted. + * Sets the delete permission status. + * + * @param delete Permission status to set + * @return the updated BlobSASPermission object. */ public BlobSASPermission delete(boolean delete) { this.delete = delete; diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobClientBuilder.java b/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobClientBuilder.java index 4774228c27648..78dcafc2babb7 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobClientBuilder.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/BlockBlobClientBuilder.java @@ -104,8 +104,6 @@ private AzureBlobStorageBuilder buildImpl() { policies.add(new BearerTokenAuthenticationPolicy(tokenCredential, String.format("%s/.default", endpoint))); } else if (sasTokenCredential != null) { policies.add(new SASTokenCredentialPolicy(sasTokenCredential)); - } else { - policies.add(new AnonymousCredentialPolicy()); } policies.add(new RequestRetryPolicy(retryOptions)); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClientBuilder.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClientBuilder.java index c1e4e6bbdaeb2..a42386041ca2e 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClientBuilder.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerClientBuilder.java @@ -101,8 +101,6 @@ private AzureBlobStorageBuilder buildImpl() { policies.add(new BearerTokenAuthenticationPolicy(tokenCredential, String.format("%s/.default", endpoint))); } else if (sasTokenCredential != null) { policies.add(new SASTokenCredentialPolicy(sasTokenCredential)); - } else { - policies.add(new AnonymousCredentialPolicy()); } policies.add(new RequestRetryPolicy(retryOptions)); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerProperties.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerProperties.java index 9cada777b6236..9aae7ae17a8cd 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerProperties.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerProperties.java @@ -6,20 +6,23 @@ import com.azure.storage.blob.models.ContainerGetPropertiesHeaders; import com.azure.storage.blob.models.PublicAccessType; -public class ContainerProperties { +import java.time.OffsetDateTime; - private PublicAccessType blobPublicAccess; +public final class ContainerProperties { - private boolean hasImmutabilityPolicy; + private final PublicAccessType blobPublicAccess; - private boolean hasLegalHold; + private final boolean hasImmutabilityPolicy; - //todo decide datetime representation for last modified time + private final boolean hasLegalHold; + + private final OffsetDateTime lastModified; ContainerProperties(ContainerGetPropertiesHeaders generatedResponseHeaders) { this.blobPublicAccess = generatedResponseHeaders.blobPublicAccess(); this.hasImmutabilityPolicy = generatedResponseHeaders.hasImmutabilityPolicy(); this.hasLegalHold = generatedResponseHeaders.hasLegalHold(); + this.lastModified = generatedResponseHeaders.lastModified(); } /** @@ -42,4 +45,11 @@ public boolean hasImmutabilityPolicy() { public boolean hasLegalHold() { return hasLegalHold; } + + /** + * @return the time the container was last modified + */ + public OffsetDateTime lastModified() { + return lastModified; + } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerSASPermission.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerSASPermission.java index e7be8f0d39a39..c6031c9edd374 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerSASPermission.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ContainerSASPermission.java @@ -13,7 +13,7 @@ * {@link ServiceSASSignatureValues} object. It is possible to construct the permissions string without this class, but * the order of the permissions is particular and this class guarantees correctness. */ -final class ContainerSASPermission { +public final class ContainerSASPermission { private boolean read; private boolean add; @@ -29,7 +29,7 @@ final class ContainerSASPermission { /** * Initializes an {@code ContainerSASPermssion} object with all fields set to false. */ - ContainerSASPermission() { + public ContainerSASPermission() { } /** @@ -75,14 +75,17 @@ public static ContainerSASPermission parse(String permString) { } /** - * Specifies Read access granted. + * @return the read permission status */ public boolean read() { return read; } /** - * Specifies Read access granted. + * Sets the read permission status. + * + * @param read Permission status to set + * @return the updated ContainerSASPermission object */ public ContainerSASPermission read(boolean read) { this.read = read; @@ -90,14 +93,17 @@ public ContainerSASPermission read(boolean read) { } /** - * Specifies Add access granted. + * @return the add permission status */ public boolean add() { return add; } /** - * Specifies Add access granted. + * Sets the add permission status. + * + * @param add Permission status to set + * @return the updated ContainerSASPermission object */ public ContainerSASPermission add(boolean add) { this.add = add; @@ -105,14 +111,17 @@ public ContainerSASPermission add(boolean add) { } /** - * Specifies Create access granted. + * @return the create permission status */ public boolean create() { return create; } /** - * Specifies Create access granted. + * Sets the create permission status. + * + * @param create Permission status to set + * @return the updated ContainerSASPermission object */ public ContainerSASPermission create(boolean create) { this.create = create; @@ -120,14 +129,17 @@ public ContainerSASPermission create(boolean create) { } /** - * Specifies Write access granted. + * @return the write permission status */ public boolean write() { return write; } /** - * Specifies Write access granted. + * Sets the write permission status. + * + * @param write Permission status to set + * @return the updated ContainerSASPermission object */ public ContainerSASPermission write(boolean write) { this.write = write; @@ -135,14 +147,17 @@ public ContainerSASPermission write(boolean write) { } /** - * Specifies Delete access granted. + * @return the delete permission status */ public boolean delete() { return delete; } /** - * Specifies Delete access granted. + * Sets the delete permission status. + * + * @param delete Permission status to set + * @return the updated ContainerSASPermission object */ public ContainerSASPermission delete(boolean delete) { this.delete = delete; @@ -150,14 +165,17 @@ public ContainerSASPermission delete(boolean delete) { } /** - * Specifies List access granted. + * @return the list permission status */ public boolean list() { return list; } /** - * Specifies List access granted. + * Sets the list permission status. + * + * @param list Permission status to set + * @return the updated ContainerSASPermission object */ public ContainerSASPermission list(boolean list) { this.list = list; diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/HTTPGetterInfo.java b/storage/client/blob/src/main/java/com/azure/storage/blob/HTTPGetterInfo.java index f868d8061d8df..6393d0a1ebbb5 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/HTTPGetterInfo.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/HTTPGetterInfo.java @@ -11,7 +11,7 @@ * HTTPGetterInfo is a passed to the getter function of a reliable download to specify parameters needed for the GET * request. */ -final class HTTPGetterInfo { +public final class HTTPGetterInfo { private long offset = 0; private Long count = null; @@ -19,14 +19,17 @@ final class HTTPGetterInfo { private String eTag = null; /** - * The start offset that should be used when creating the HTTP GET request's Range header. Defaults to 0. + * @return the start offset used when creating the Range header. Defaults to 0. */ public long offset() { return offset; } /** - * The start offset that should be used when creating the HTTP GET request's Range header. Defaults to 0. + * Sets the start offset that is used when creating the Range header. If unchanged this will default to 0. + * + * @param offset Start offset + * @return the updated HTTPGetterInfo object */ public HTTPGetterInfo offset(long offset) { this.offset = offset; @@ -34,16 +37,19 @@ public HTTPGetterInfo offset(long offset) { } /** - * The count of bytes that should be used to calculate the end offset when creating the HTTP GET request's Range - * header. {@code} null is the default and indicates that the entire rest of the blob should be retrieved. + * @return the count of bytes used to calculate the end offset when creating the Range header. {@code} null is the + * default and indicates that the entire rest of the blob should be retrieved. */ public Long count() { return count; } /** - * The count of bytes that should be used to calculate the end offset when creating the HTTP GET request's Range - * header. {@code} null is the default and indicates that the entire rest of the blob should be retrieved. + * Sets the count of bytes used to calculate the end offset when creating the Range header. {@code} null is the + * default and indicates that the entire rest of the blob should be retrieved. + * + * @param count Count of bytes + * @return the updated HTTPGetterInfo object */ public HTTPGetterInfo count(Long count) { if (count != null) { @@ -54,18 +60,21 @@ public HTTPGetterInfo count(Long count) { } /** - * The resource's etag that should be used when creating the HTTP GET request's If-Match header. Note that the - * Etag is returned with any operation that modifies the resource and by a call to {@link - * BlobClient#getProperties(BlobAccessConditions, Duration)}. Defaults to null. + * @return the eTag used when creating If-Match header. eTag is returned with any operation that modifies the + * resource and when retrieving {@link BlobClient#getProperties(BlobAccessConditions, Duration) properties}. + * Defaults to null. */ public String eTag() { return eTag; } /** - * The resource's etag that should be used when creating the HTTP GET request's If-Match header. Note that the - * Etag is returned with any operation that modifies the resource and by a call to {@link - * BlobClient#getProperties(BlobAccessConditions, Duration)}. Defaults to null. + * Sets the eTag used when creating If-Match header. eTag is returned with any operation that modifies the + * resource and when retrieving {@link BlobClient#getProperties(BlobAccessConditions, Duration) properties}. + * Defaults to null. + * + * @param eTag Resource's eTag + * @return the updated HTTPGetterInfo object */ public HTTPGetterInfo eTag(String eTag) { this.eTag = eTag; diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/IPRange.java b/storage/client/blob/src/main/java/com/azure/storage/blob/IPRange.java index 24665c7aaedda..c160fcd539dd3 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/IPRange.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/IPRange.java @@ -8,13 +8,13 @@ * set if it is not desired to confine the sas permissions to an IP range. Please refer to * {@link AccountSASSignatureValues} or {@link ServiceSASSignatureValues} for more information. */ -final class IPRange { +public final class IPRange { private String ipMin; private String ipMax; - IPRange() { + public IPRange() { } /** @@ -36,14 +36,17 @@ public static IPRange parse(String rangeStr) { } /** - * The minimum IP address of the range. + * @return the minimum IP address of the range. */ public String ipMin() { return ipMin; } /** - * The minimum IP address of the range. + * Sets the minimum IP address of the range. + * + * @param ipMin Minimum IP of the range + * @return the updated IPRange object */ public IPRange ipMin(String ipMin) { this.ipMin = ipMin; @@ -51,14 +54,18 @@ public IPRange ipMin(String ipMin) { } /** - * The maximum IP address of the range. + * + * @return the maximum IP address of the range. */ public String ipMax() { return ipMax; } /** - * The maximum IP address of the range. + * Sets the maximum IP address of the range. + * + * @param ipMax Maximum IP of the range + * @return the updated IPRange object */ public IPRange ipMax(String ipMax) { this.ipMax = ipMax; diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/IProgressReceiver.java b/storage/client/blob/src/main/java/com/azure/storage/blob/IProgressReceiver.java index ab336e1862892..70d85fe58090f 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/IProgressReceiver.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/IProgressReceiver.java @@ -3,20 +3,20 @@ package com.azure.storage.blob; +import reactor.core.publisher.Flux; + /** * An {@code IProgressReceiver} is an object that can be used to report progress on network transfers. When specified on * transfer operations, the {@code reportProgress} method will be called periodically with the total number of bytes - * transferred. The user may configure this method to report progress in whatever format desired. It is recommended - * that this type be used in conjunction with - * {@link ProgressReporter#addProgressReporting(reactor.core.publisher.Flux, IProgressReceiver)}. + * transferred. The user may configure this method to report progress in wha tever format desired. It is recommended + * that this type be used in conjunction with {@link ProgressReporter#addProgressReporting(Flux, IProgressReceiver)}. */ interface IProgressReceiver { /** * The callback function invoked as progress is reported. * - * @param bytesTransferred - * The total number of bytes transferred during this transaction. + * @param bytesTransferred The total number of bytes transferred during this transaction. */ void reportProgress(long bytesTransferred); } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClientBuilder.java b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClientBuilder.java index 77010e0607f84..c6c93b48addf1 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClientBuilder.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/PageBlobClientBuilder.java @@ -100,8 +100,6 @@ private AzureBlobStorageBuilder buildImpl() { policies.add(new BearerTokenAuthenticationPolicy(tokenCredential, String.format("%s/.default", endpoint))); } else if (sasTokenCredential != null) { policies.add(new SASTokenCredentialPolicy(sasTokenCredential)); - } else { - policies.add(new AnonymousCredentialPolicy()); } policies.add(new RequestRetryPolicy(retryOptions)); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ProgressReporter.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ProgressReporter.java index fc882d4c30aa4..4ac0ce9cda2f3 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ProgressReporter.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ProgressReporter.java @@ -11,7 +11,7 @@ import java.util.concurrent.locks.Lock; /** - * {@code ProgressReporterImpl} offers a convenient way to add progress tracking to a given Flowable. + * {@code ProgressReporter} offers a convenient way to add progress tracking to a given Flux. */ final class ProgressReporter { @@ -35,29 +35,28 @@ void rewindProgress() { } Flux addProgressReporting(Flux data) { - return Mono.just(this) - .flatMapMany(progressReporter -> { - /* - Each time there is a new subscription, we will rewind the progress. This is desirable specifically - for retries, which resubscribe on each try. The first time this flowable is subscribed to, the - rewind will be a noop as there will have been no progress made. Subsequent rewinds will work as - expected. - */ - progressReporter.rewindProgress(); - /* - Every time we emit some data, report it to the Tracker, which will pass it on to the end user. - */ - return data.doOnNext(buffer -> - progressReporter.reportProgress(buffer.remaining())); - }); + return Mono.just(this).flatMapMany(progressReporter -> { + /* + Each time there is a new subscription, we will rewind the progress. This is desirable specifically + for retries, which resubscribe on each try. The first time this flowable is subscribed to, the + rewind will be a noop as there will have been no progress made. Subsequent rewinds will work as + expected. + */ + progressReporter.rewindProgress(); + + /* + Every time we emit some data, report it to the Tracker, which will pass it on to the end user. + */ + return data.doOnNext(buffer -> progressReporter.reportProgress(buffer.remaining())); + }); } } /** - * This type is used to keep track of the total amount of data transferred for a single request. This is the type - * we will use when the customer uses the factory to add progress reporting to their Flowable. We need this - * additional type because we can't keep local state directly as lambdas require captured local variables to be - * effectively final. + * This type is used to keep track of the total amount of data transferred for a single request. This is the type we + * will use when the customer uses the factory to add progress reporting to their Flowable. We need this additional + * type because we can't keep local state directly as lambdas require captured local variables to be effectively + * final. */ private static class SequentialProgressReporter extends ProgressReporterImpl { SequentialProgressReporter(IProgressReceiver progressReceiver) { @@ -129,24 +128,19 @@ public void rewindProgress() { } /** - * Adds progress reporting functionality to the given {@code Flux}. Each subscription (and therefore each - * retry) will rewind the progress reported so as not to over-report. The data reported will be the total amount - * of data emitted so far, or the "current position" of the Flowable. + * Adds progress reporting functionality to the given {@code Flux}. Each subscription (and therefore each retry) + * will rewind the progress reported so as not to over-report. The data reported will be the total amount of data + * emitted so far, or the "current position" of the Flowable. * - * @param data - * The data whose transfer progress is to be tracked. - * @param progressReceiver - * {@link IProgressReceiver} - * - * @return A {@code Flux} that emits the same data as the source but calls a callback to report the total amount - * of data emitted so far. - * - * @apiNote ## Sample Code \n - * [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=progress "Sample code for ProgressReporterFactor.addProgressReporting")] \n - * For more samples, please see the [Samples file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) + * @param data The data whose transfer progress is to be tracked. + * @param progressReceiver {@link IProgressReceiver} + * @return A {@code Flux} that emits the same data as the source but calls a callback to report the total amount of + * data emitted so far. + * @apiNote ## Sample Code \n [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=progress + * "Sample code for ProgressReporterFactor.addProgressReporting")] \n For more samples, please see the [Samples + * file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) */ - public static Flux addProgressReporting(Flux data, - IProgressReceiver progressReceiver) { + public static Flux addProgressReporting(Flux data, IProgressReceiver progressReceiver) { if (progressReceiver == null) { return data; } else { @@ -155,8 +149,7 @@ public static Flux addProgressReporting(Flux data, } } - static Flux addParallelProgressReporting(Flux data, - IProgressReceiver progressReceiver, Lock lock, AtomicLong totalProgress) { + static Flux addParallelProgressReporting(Flux data, IProgressReceiver progressReceiver, Lock lock, AtomicLong totalProgress) { if (progressReceiver == null) { return data; } else { diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/SASProtocol.java b/storage/client/blob/src/main/java/com/azure/storage/blob/SASProtocol.java index 64df03714ecb5..475e6f4289f6f 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/SASProtocol.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/SASProtocol.java @@ -9,7 +9,7 @@ * Specifies the set of possible permissions for a shared access signature protocol. Values of this type can be used * to set the fields on the {@link AccountSASSignatureValues} and {@link ServiceSASSignatureValues} types. */ -enum SASProtocol { +public enum SASProtocol { /** * Permission to use SAS only through https granted. */ diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/SASQueryParameters.java b/storage/client/blob/src/main/java/com/azure/storage/blob/SASQueryParameters.java index f5b70a957baf6..9c6acf1d65702 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/SASQueryParameters.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/SASQueryParameters.java @@ -14,12 +14,12 @@ /** * Represents the components that make up an Azure Storage SAS' query parameters. This type is not constructed directly * by the user; it is only generated by the {@link AccountSASSignatureValues} and {@link ServiceSASSignatureValues} - * types. Once generated, it can be set on a {@link BlobURLParts} object to be constructed as part of a URL or it can - * be encoded into a {@code String} and appended to a URL directly (though caution should be taken here in case there - * are existing query parameters, which might affect the appropriate means of appending these query parameters). - * NOTE: Instances of this class are immutable to ensure thread safety. + * types. Once generated, it can be set on a {@link BlobURLParts} object to be constructed as part of a URL or it can be + * encoded into a {@code String} and appended to a URL directly (though caution should be taken here in case there are + * existing query parameters, which might affect the appropriate means of appending these query parameters). NOTE: + * Instances of this class are immutable to ensure thread safety. */ -final class SASQueryParameters { +public final class SASQueryParameters { private final String version; @@ -69,7 +69,8 @@ final class SASQueryParameters { * Creates a new {@link SASQueryParameters} object. * * @param queryParamsMap All query parameters for the request as key-value pairs - * @param removeSASParametersFromMap When {@code true}, the SAS query parameters will be removed from queryParamsMap + * @param removeSASParametersFromMap When {@code true}, the SAS query parameters will be removed from + * queryParamsMap */ public SASQueryParameters(Map queryParamsMap, boolean removeSASParametersFromMap) { this.version = getQueryParameter(queryParamsMap, Constants.UrlConstants.SAS_SERVICE_VERSION, removeSASParametersFromMap); @@ -115,36 +116,27 @@ private T getQueryParameter(Map parameters, String name, B /** - * Creates a new {@link SASQueryParameters} object. These objects are only created internally by - * *SASSignatureValues classes. + * Creates a new {@link SASQueryParameters} object. These objects are only created internally by SASSignatureValues + * classes. * - * @param version - * A {@code String} representing the storage version. - * @param services - * A {@code String} representing the storage services being accessed (only for Account SAS). - * @param resourceTypes - * A {@code String} representing the storage resource types being accessed (only for Account SAS). - * @param protocol - * A {@code String} representing the allowed HTTP protocol(s) or {@code null}. - * @param startTime - * A {@code java.util.Date} representing the start time for this SAS token or {@code null}. - * @param expiryTime - * A {@code java.util.Date} representing the expiry time for this SAS token. - * @param ipRange - * A {@link IPRange} representing the range of valid IP addresses for this SAS token or {@code null}. - * @param identifier - * A {@code String} representing the signed identifier (only for Service SAS) or {@code null}. - * @param resource - * A {@code String} representing the storage container or blob (only for Service SAS). - * @param permissions - * A {@code String} representing the storage permissions or {@code null}. - * @param signature - * A {@code String} representing the signature for the SAS token. + * @param version A {@code String} representing the storage version. + * @param services A {@code String} representing the storage services being accessed (only for Account SAS). + * @param resourceTypes A {@code String} representing the storage resource types being accessed (only for Account + * SAS). + * @param protocol A {@code String} representing the allowed HTTP protocol(s) or {@code null}. + * @param startTime A {@code java.util.Date} representing the start time for this SAS token or {@code null}. + * @param expiryTime A {@code java.util.Date} representing the expiry time for this SAS token. + * @param ipRange A {@link IPRange} representing the range of valid IP addresses for this SAS token or {@code + * null}. + * @param identifier A {@code String} representing the signed identifier (only for Service SAS) or {@code null}. + * @param resource A {@code String} representing the storage container or blob (only for Service SAS). + * @param permissions A {@code String} representing the storage permissions or {@code null}. + * @param signature A {@code String} representing the signature for the SAS token. */ SASQueryParameters(String version, String services, String resourceTypes, SASProtocol protocol, - OffsetDateTime startTime, OffsetDateTime expiryTime, IPRange ipRange, String identifier, - String resource, String permissions, String signature, String cacheControl, String contentDisposition, - String contentEncoding, String contentLanguage, String contentType, UserDelegationKey key) { + OffsetDateTime startTime, OffsetDateTime expiryTime, IPRange ipRange, String identifier, + String resource, String permissions, String signature, String cacheControl, String contentDisposition, + String contentEncoding, String contentLanguage, String contentType, UserDelegationKey key) { this.version = version; this.services = services; @@ -196,8 +188,8 @@ public String services() { } /** - * @return The storage resource types being accessed (only for Account SAS). Please refer to - * {@link AccountSASResourceType} for more details. + * @return The storage resource types being accessed (only for Account SAS). Please refer to {@link + * AccountSASResourceType} for more details. */ public String resourceTypes() { return resourceTypes; @@ -248,8 +240,8 @@ public String resource() { } /** - * @return Please refer to {@link AccountSASPermission}, {@link BlobSASPermission}, or {@link ContainerSASPermission} - * for more details. + * @return Please refer to {@link AccountSASPermission}, {@link BlobSASPermission}, or {@link + * ContainerSASPermission} for more details. */ public String permissions() { return permissions; @@ -297,38 +289,56 @@ public String contentType() { return contentType; } + /** + * @return the key OBJECT_ID + */ public String keyOid() { return keyOid; } + /** + * @return the key TENANT_ID + */ public String keyTid() { return keyTid; } + /** + * @return when the key becomes valid + */ public OffsetDateTime keyStart() { return keyStart; } + /** + * @return when the key becomes no longer valid + */ public OffsetDateTime keyExpiry() { return keyExpiry; } + /** + * @return Services allowed by the key + */ public String keyService() { return keyService; } + /** + * @return Version of the key + */ public String keyVersion() { return keyVersion; } UserDelegationKey userDelegationKey() { return new UserDelegationKey() - .signedExpiry(this.keyExpiry) - .signedOid(this.keyOid) - .signedService(this.keyService) - .signedStart(this.keyStart) - .signedTid(this.keyTid) - .signedVersion(this.keyVersion); + .signedExpiry(this.keyExpiry) + .signedOid(this.keyOid) + .signedService(this.keyService) + .signedStart(this.keyStart) + .signedTid(this.keyTid) + .signedVersion(this.keyVersion); } private void tryAppendQueryParameter(StringBuilder sb, String param, Object value) { diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java index 031ec5d11511a..4f49475be80dc 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java @@ -24,11 +24,11 @@ * Please see here for * more details on each value, including which are required. * - * @apiNote ## Sample Code \n [!code-java[Sample_Code](../azure-storage-java/src/test/java/com/microsoft/azure/storage/Samples.java?name=service_sas - * "Sample code for ServiceSASSignatureValues")] \n For more samples, please see the [Samples - * file](%https://github.com/Azure/azure-storage-java/blob/master/src/test/java/com/microsoft/azure/storage/Samples.java) + *

Please see + * here + * for additional samples.

*/ -final class ServiceSASSignatureValues { +public final class ServiceSASSignatureValues { private String version = Constants.HeaderConstants.TARGET_STORAGE_VERSION; @@ -63,14 +63,14 @@ final class ServiceSASSignatureValues { /** * Creates an object with empty values for all fields. */ - ServiceSASSignatureValues() { + public ServiceSASSignatureValues() { } /** * Creates an object with the specified expiry time and permissions * - * @param expiryTime - * @param permissions + * @param expiryTime Time the SAS becomes valid + * @param permissions Permissions granted by the SAS */ ServiceSASSignatureValues(OffsetDateTime expiryTime, String permissions) { this.expiryTime = expiryTime; @@ -80,7 +80,7 @@ final class ServiceSASSignatureValues { /** * Creates an object with the specified identifier * - * @param identifier + * @param identifier Identifier for the SAS */ ServiceSASSignatureValues(String identifier) { this.identifier = identifier; @@ -106,16 +106,19 @@ final class ServiceSASSignatureValues { } /** - * The version of the service this SAS will target. If not specified, it will default to the version targeted by the - * library. + * @return the version of the service this SAS will target. If not specified, it will default to the version targeted + * by the library. */ public String version() { return version; } /** - * The version of the service this SAS will target. If not specified, it will default to the version targeted by the - * library. + * Sets the version of the service this SAS will target. If not specified, it will default to the version targeted + * by the library. + * + * @param version Version to target + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues version(String version) { this.version = version; @@ -123,14 +126,17 @@ public ServiceSASSignatureValues version(String version) { } /** - * {@link SASProtocol} + * @return the {@link SASProtocol} which determines the protocols allowed by the SAS. */ public SASProtocol protocol() { return protocol; } /** - * {@link SASProtocol} + * Sets the {@link SASProtocol} which determines the protocols allowed by the SAS. + * + * @param protocol Protocol for the SAS + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues protocol(SASProtocol protocol) { this.protocol = protocol; @@ -138,14 +144,17 @@ public ServiceSASSignatureValues protocol(SASProtocol protocol) { } /** - * When the SAS will take effect. + * @return when the SAS will take effect. */ public OffsetDateTime startTime() { return startTime; } /** - * When the SAS will take effect. + * Sets when the SAS will take effect. + * + * @param startTime When the SAS takes effect + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues startTime(OffsetDateTime startTime) { this.startTime = startTime; @@ -153,14 +162,17 @@ public ServiceSASSignatureValues startTime(OffsetDateTime startTime) { } /** - * The time after which the SAS will no longer work. + * @return the time after which the SAS will no longer work. */ public OffsetDateTime expiryTime() { return expiryTime; } /** - * The time after which the SAS will no longer work. + * Sets the time after which the SAS will no longer work. + * + * @param expiryTime When the SAS will no longer work + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues expiryTime(OffsetDateTime expiryTime) { this.expiryTime = expiryTime; @@ -168,16 +180,19 @@ public ServiceSASSignatureValues expiryTime(OffsetDateTime expiryTime) { } /** - * Please refer to either {@link ContainerSASPermission} or {@link BlobSASPermission} depending on the resource - * being accessed for help constructing the permissions string. + * @return the permissions string allowed by the SAS. Please refer to either {@link ContainerSASPermission} or + * {@link BlobSASPermission} depending on the resource being accessed for help determining the pernissions allowed. */ public String permissions() { return permissions; } /** - * Please refer to either {@link ContainerSASPermission} or {@link BlobSASPermission} depending on the resource - * being accessed for help constructing the permissions string. + * Sets the permissions string allowed by the SAS. Please refer to either {@link ContainerSASPermission} or + * {@link BlobSASPermission} depending on the resource being accessed for help constructing the permissions string. + * + * @param permissions Permissions string for the SAS + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues permissions(String permissions) { this.permissions = permissions; @@ -185,14 +200,17 @@ public ServiceSASSignatureValues permissions(String permissions) { } /** - * {@link IPRange} + * @return the {@link IPRange} which determines the IP ranges that are allowed to use the SAS. */ public IPRange ipRange() { return ipRange; } /** - * {@link IPRange} + * Sets the {@link IPRange} which determines the IP ranges that are allowed to use the SAS. + * + * @param ipRange Allowed IP range to set + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues ipRange(IPRange ipRange) { this.ipRange = ipRange; @@ -200,14 +218,17 @@ public ServiceSASSignatureValues ipRange(IPRange ipRange) { } /** - * The resource the SAS user may access. + * @return the resource the SAS user may access. */ public String resource() { return resource; } /** - * The resource the SAS user may access. + * Sets the resource the SAS user may access. + * + * @param resource Allowed resources string to set + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues resource(String resource) { this.resource = resource; @@ -215,14 +236,17 @@ public ServiceSASSignatureValues resource(String resource) { } /** - * The canonical name of the object the SAS user may access. + * @return the canonical name of the object the SAS user may access. */ public String canonicalName() { return canonicalName; } /** - * The canonical name of the object the SAS user may access. + * Sets the canonical name of the object the SAS user may access. + * + * @param canonicalName Canonical name of the object the SAS grants access + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues canonicalName(String canonicalName) { this.canonicalName = canonicalName; @@ -230,34 +254,38 @@ public ServiceSASSignatureValues canonicalName(String canonicalName) { } /** - * The canonical name of the object the SAS user may access. + * Sets the canonical name of the object the SAS user may access. Constructs a canonical name of + * "/blob/{accountName}{Path of urlString}". * - * @throws RuntimeException If urlString is a malformed URL. + * @param urlString URL string that contains the path to the object + * @param accountName Name of the account that contains the object + * @return the updated ServiceSASSignatureValues object + * @throws RuntimeException If {@code urlString} is a malformed URL. */ public ServiceSASSignatureValues canonicalName(String urlString, String accountName) { - URL url = null; + URL url; try { url = new URL(urlString); } catch (MalformedURLException e) { throw new RuntimeException(e); } - StringBuilder canonicalName = new StringBuilder("/blob"); - canonicalName.append('/').append(accountName).append(url.getPath()); - this.canonicalName = canonicalName.toString(); - + this.canonicalName = String.format("/blob/%s%s", accountName, url.getPath()); return this; } /** - * The specific snapshot the SAS user may access. + * @return the specific snapshot the SAS user may access. */ public String snapshotId() { return this.snapshotId; } /** - * The specific snapshot the SAS user may access. + * Sets the specific snapshot the SAS user may access. + * + * @param snapshotId Identifier of the snapshot + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues snapshotId(String snapshotId) { this.snapshotId = snapshotId; @@ -265,7 +293,7 @@ public ServiceSASSignatureValues snapshotId(String snapshotId) { } /** - * The name of the access policy on the container this SAS references if any. Please see + * @return the name of the access policy on the container this SAS references if any. Please see * here * for more information. */ @@ -274,9 +302,12 @@ public String identifier() { } /** - * The name of the access policy on the container this SAS references if any. Please see + * Sets the name of the access policy on the container this SAS references if any. Please see * here * for more information. + * + * @param identifier Name of the access policy + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues identifier(String identifier) { this.identifier = identifier; @@ -284,14 +315,17 @@ public ServiceSASSignatureValues identifier(String identifier) { } /** - * The cache-control header for the SAS. + * @return the cache-control header for the SAS. */ public String cacheControl() { return cacheControl; } /** - * The cache-control header for the SAS. + * Sets the cache-control header for the SAS. + * + * @param cacheControl Cache-Control header value + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues cacheControl(String cacheControl) { this.cacheControl = cacheControl; @@ -299,14 +333,17 @@ public ServiceSASSignatureValues cacheControl(String cacheControl) { } /** - * The content-disposition header for the SAS. + * @return the content-disposition header for the SAS. */ public String contentDisposition() { return contentDisposition; } /** - * The content-disposition header for the SAS. + * Sets the content-disposition header for the SAS. + * + * @param contentDisposition Content-Disposition header value + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues contentDisposition(String contentDisposition) { this.contentDisposition = contentDisposition; @@ -314,14 +351,17 @@ public ServiceSASSignatureValues contentDisposition(String contentDisposition) { } /** - * The content-encoding header for the SAS. + * @return the content-encoding header for the SAS. */ public String contentEncoding() { return contentEncoding; } /** - * The content-encoding header for the SAS. + * Sets the content-encoding header for the SAS. + * + * @param contentEncoding Content-Encoding header value + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues contentEncoding(String contentEncoding) { this.contentEncoding = contentEncoding; @@ -329,14 +369,17 @@ public ServiceSASSignatureValues contentEncoding(String contentEncoding) { } /** - * The content-language header for the SAS. + * @return the content-language header for the SAS. */ public String contentLanguage() { return contentLanguage; } /** - * The content-language header for the SAS. + * Sets the content-language header for the SAS. + * + * @param contentLanguage Content-Language header value + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues contentLanguage(String contentLanguage) { this.contentLanguage = contentLanguage; @@ -344,14 +387,17 @@ public ServiceSASSignatureValues contentLanguage(String contentLanguage) { } /** - * The content-type header for the SAS. + * @return the content-type header for the SAS. */ public String contentType() { return contentType; } /** - * The content-type header for the SAS. + * Sets the content-type header for the SAS. + * + * @param contentType Content-Type header value + * @return the updated ServiceSASSignatureValues object */ public ServiceSASSignatureValues contentType(String contentType) { this.contentType = contentType; diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClientBuilder.java b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClientBuilder.java index 0aff9d26aa029..676574a9c2382 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClientBuilder.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/StorageClientBuilder.java @@ -95,8 +95,6 @@ private AzureBlobStorageBuilder buildImpl() { policies.add(new BearerTokenAuthenticationPolicy(tokenCredential, String.format("%s/.default", endpoint))); } else if (sasTokenCredential != null) { policies.add(new SASTokenCredentialPolicy(sasTokenCredential)); - } else { - policies.add(new AnonymousCredentialPolicy()); } policies.add(new RequestRetryPolicy(retryOptions)); diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/UnexpectedLengthException.java b/storage/client/blob/src/main/java/com/azure/storage/blob/UnexpectedLengthException.java index e3f8a52503d47..3fa8b292768d3 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/UnexpectedLengthException.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/UnexpectedLengthException.java @@ -1,19 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.azure.storage.blob; -public class UnexpectedLengthException extends IllegalStateException { +/** + * This exception class represents an error when the specified input length doesn't match the data length. + */ +public final class UnexpectedLengthException extends IllegalStateException { private final long bytesRead; private final long bytesExpected; - public UnexpectedLengthException(String message, long bytesRead, long bytesExpected) { + UnexpectedLengthException(String message, long bytesRead, long bytesExpected) { super(message); this.bytesRead = bytesRead; this.bytesExpected = bytesExpected; } + /** + * @return the number of bytes read from the input + */ public long bytesRead() { return this.bytesRead; } + /** + * @return the number of bytes that were expected to be read from the input + */ public long bytesExpected() { return this.bytesExpected; } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/Utility.java b/storage/client/blob/src/main/java/com/azure/storage/blob/Utility.java index 252a5e83bbaf2..66dbd4096de73 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/Utility.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/Utility.java @@ -264,8 +264,7 @@ static Mono postProcessResponse(Mono s) { methods to conveniently access important values. */ private static Mono addErrorWrappingToSingle(Mono s) { - return s.onErrorResume( - StorageErrorException.class, + return s.onErrorResume(StorageErrorException.class, e -> e.response() .bodyAsString() .switchIfEmpty(Mono.just("")) diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/ProgressReporterTest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/ProgressReporterTest.groovy index be6d1d3ce258d..164dbc22009d1 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/ProgressReporterTest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/ProgressReporterTest.groovy @@ -3,6 +3,8 @@ package com.azure.storage.blob +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled import reactor.core.publisher.Flux import java.nio.ByteBuffer @@ -38,11 +40,16 @@ class ProgressReporterTest extends APISpec { IProgressReceiver mockReceiver = Mock(IProgressReceiver) ByteBuffer buffer = getRandomData(1 * 1024 * 1024) - ProgressReporter.addProgressReporting(Flux.just(buffer), mockReceiver) + Flux data = ProgressReporter.addProgressReporting(Flux.just(buffer), mockReceiver) + .map({ it -> Unpooled.wrappedBuffer(it) }) when: - BlockBlobClient bu = cu.getBlockBlobClient(generateBlobName()) - bu.upload(new ByteArrayInputStream(buffer.array()), buffer.remaining()) + BlockBlobAsyncClient bu = new BlockBlobClientBuilder() + .endpoint(cu.getContainerUrl().toString()) + .blobName(generateBlobName()) + .credential(primaryCreds) + .buildAsyncClient() + bu.upload(data, buffer.remaining()).block() then: /* @@ -74,7 +81,7 @@ class ProgressReporterTest extends APISpec { data.subscribe() data2.subscribe() - sleep(3000) // These Flowables should complete quickly, but we don't want to block or it'll order everything + sleep(3000) // These Fluxes should complete quickly, but we don't want to block or it'll order everything then: /* @@ -86,13 +93,13 @@ class ProgressReporterTest extends APISpec { /* There should be 12 calls total, but either one or two of them could be reporting the total length, so we can only guarantee four calls with an unknown parameter. This test doesn't strictly mimic the network as - there would never be concurrent subscriptions to the same Flowable as may be the case here, but it is good + there would never be concurrent subscriptions to the same Flux as may be the case here, but it is good enough. */ (10..11) * mockReceiver.reportProgress(_) /* - We should never report more progress than the 60 total (30 from each Flowable--Resubscribing is a retry and + We should never report more progress than the 60 total (30 from each Flux--Resubscribing is a retry and therefore rewinds). */ 0 * mockReceiver.reportProgress({it > 60}) diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/SASTest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/SASTest.groovy index 1ebf9860f2ac3..fe40a9a45b923 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/SASTest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/SASTest.groovy @@ -6,7 +6,6 @@ package com.azure.storage.blob import com.azure.storage.blob.models.AccessPolicy import com.azure.storage.blob.models.BlobRange import com.azure.storage.blob.models.SignedIdentifier -import com.azure.storage.blob.models.StorageErrorCode import com.azure.storage.blob.models.UserDelegationKey import com.azure.storage.common.credentials.SASTokenCredential import com.azure.storage.common.credentials.SharedKeyCredential @@ -17,24 +16,10 @@ import java.time.OffsetDateTime import java.time.ZoneOffset class SASTest extends APISpec { - - def "responseError"() { - when: - cu.listBlobsFlat() - - then: - def e = thrown(StorageException) - e.errorCode() == StorageErrorCode.INVALID_QUERY_PARAMETER_VALUE - e.statusCode() == 400 - e.message().contains("Value for one of the query parameters specified in the request URI is invalid.") - e.getMessage().contains(" Date: Fri, 19 Jul 2019 11:43:48 -0700 Subject: [PATCH 13/19] Fixing more unit tests --- .../java/com/azure/storage/blob/IPRange.java | 3 + .../blob/ServiceSASSignatureValues.java | 44 +++++---- .../common/policy/RequestRetryOptions.java | 22 ++++- .../com/azure/storage/blob/APISpec.groovy | 1 + .../blob/DownloadResponseMockFlux.java | 8 +- .../com/azure/storage/blob/HelperTest.groovy | 70 +++++++++----- .../storage/blob/RequestRetryTestFactory.java | 7 +- .../com/azure/storage/blob/SASTest.groovy | 92 ++++++++++--------- 8 files changed, 151 insertions(+), 96 deletions(-) diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/IPRange.java b/storage/client/blob/src/main/java/com/azure/storage/blob/IPRange.java index c160fcd539dd3..9e66189fa904e 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/IPRange.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/IPRange.java @@ -14,6 +14,9 @@ public final class IPRange { private String ipMax; + /** + * Constructs an empty IPRange. + */ public IPRange() { } diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java index 4f49475be80dc..e82efd73741cb 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java @@ -466,21 +466,35 @@ private void assertGenerateOK(boolean usingUserDelegation) { Utility.assertNotNull("version", this.version); Utility.assertNotNull("canonicalName", this.canonicalName); - // Ensure either (expiryTime and permissions) or (identifier) is set - if (this.identifier == null && (this.expiryTime == null || this.permissions == null)) { - // Identifier is not required if user delegation is being used - if (!usingUserDelegation) { - Utility.assertNotNull("identifier", this.identifier); - } - } else { + // If a SignedIdentifier or UserDelegationKey isn't being used expiryTime and permissions must be set. + if (!usingUserDelegation && this.identifier == null) { Utility.assertNotNull("expiryTime", this.expiryTime); Utility.assertNotNull("permissions", this.permissions); } - if (this.resource != null && this.resource.equals(Constants.UrlConstants.SAS_CONTAINER_CONSTANT)) { - if (this.snapshotId != null) { - throw new IllegalArgumentException("Cannot set a snapshotId without resource being a blob."); - } + // When using an identifier or delegation key permissions and expiry time are allowed to be null. +// if (this.expiryTime == null && this.permissions == null) { +// if (!usingUserDelegation) { +// Utility.assertNotNull("identifier", this.identifier); +// } +// } else { +// Utility.assertNotNull("expiryTime", this.expiryTime); +// Utility.assertNotNull("permissions", this.permissions); +// } + +// // Ensure either (expiryTime and permissions) or (identifier) is set +// if (this.identifier == null && (this.expiryTime == null && this.permissions == null)) { +// // Identifier is not required if user delegation is being used +// if (!usingUserDelegation) { +// Utility.assertNotNull("identifier", this.identifier); +// } +// } else { +// Utility.assertNotNull("expiryTime", this.expiryTime); +// Utility.assertNotNull("permissions", this.permissions); +// } + + if (Constants.UrlConstants.SAS_CONTAINER_CONSTANT.equals(this.resource) && this.snapshotId != null) { + throw new IllegalArgumentException("Cannot set a snapshotId without resource being a blob."); } } @@ -528,12 +542,4 @@ private String stringToSign(final UserDelegationKey key) { this.contentType == null ? "" : this.contentType ); } - - private String getResource() { - if (Constants.UrlConstants.SAS_BLOB_CONSTANT.equals(resource) && this.snapshotId != null) { - return Constants.UrlConstants.SAS_BLOB_SNAPSHOT_CONSTANT; - } else { - return resource; - } - } } diff --git a/storage/client/blob/src/main/java/com/azure/storage/common/policy/RequestRetryOptions.java b/storage/client/blob/src/main/java/com/azure/storage/common/policy/RequestRetryOptions.java index 7e6ae9bcf3dbb..ae0519ee6a03c 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/common/policy/RequestRetryOptions.java +++ b/storage/client/blob/src/main/java/com/azure/storage/common/policy/RequestRetryOptions.java @@ -16,11 +16,8 @@ public final class RequestRetryOptions { private final int tryTimeout; private final long retryDelayInMs; private final long maxRetryDelayInMs; - /** - * A {@link RetryPolicyType} telling the pipeline what kind of retry policy to use. - */ - private RetryPolicyType retryPolicyType; - private String secondaryHost; + private final RetryPolicyType retryPolicyType; + private final String secondaryHost; /** * Constructor with default retry values: Exponential backoff, maxTries=4, tryTimeout=30, retryDelayInMs=4000, @@ -107,22 +104,37 @@ public RequestRetryOptions(RetryPolicyType retryPolicyType, Integer maxTries, In this.secondaryHost = secondaryHost; } + /** + * @return the maximum number attempts that will be retried before the operation finally fails. + */ public int maxTries() { return this.maxTries; } + /** + * @return the timeout in seconds allowed for each retry operation. + */ public int tryTimeout() { return this.tryTimeout; } + /** + * @return the secondary host that retries could be attempted against. + */ public String secondaryHost() { return this.secondaryHost; } + /** + * @return the delay in milliseconds between retry attempts. + */ public long retryDelayInMs() { return retryDelayInMs; } + /** + * @return the maximum delay in milliseconds between retry attempts. + */ public long maxRetryDelayInMs() { return maxRetryDelayInMs; } diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy index 8d5eb9030e672..fca232c4a905a 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/APISpec.groovy @@ -561,6 +561,7 @@ class APISpec extends Specification { return new StorageClientBuilder() .endpoint(String.format("https://%s.blob.core.windows.net/", primaryCreds.accountName())) .credential(new EnvironmentCredential()) // AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET + .httpLogDetailLevel(HttpLogDetailLevel.BODY_AND_HEADERS) .buildClient() } diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/DownloadResponseMockFlux.java b/storage/client/blob/src/test/java/com/azure/storage/blob/DownloadResponseMockFlux.java index 85df7c38a355f..1244ec6b7bde4 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/DownloadResponseMockFlux.java +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/DownloadResponseMockFlux.java @@ -33,7 +33,7 @@ class DownloadResponseMockFlux extends Flux { private HTTPGetterInfo info; private ByteBuffer scenarioData; - public DownloadResponseMockFlux(int scenario) { + DownloadResponseMockFlux(int scenario) { this.scenario = scenario; switch (this.scenario) { case DR_TEST_SCENARIO_SUCCESSFUL_ONE_CHUNK: @@ -53,11 +53,11 @@ public DownloadResponseMockFlux(int scenario) { } } - public ByteBuffer getScenarioData() { + ByteBuffer getScenarioData() { return this.scenarioData; } - public int getTryNumber() { + int getTryNumber() { return this.tryNumber; } @@ -146,7 +146,7 @@ public void subscribe(CoreSubscriber subscriber) { } } - public Mono getter(HTTPGetterInfo info) { + Mono getter(HTTPGetterInfo info) { this.tryNumber++; this.info = info; BlobsDownloadResponse rawResponse = new BlobsDownloadResponse(null, 200, new HttpHeaders(), this, new BlobDownloadHeaders()); diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy index 9d8bd861418e5..661449c85c7bf 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy @@ -434,9 +434,14 @@ class HelperTest extends APISpec { v.permissions("") } + if (snapId != null) { + v.resource(Constants.UrlConstants.SAS_BLOB_SNAPSHOT_CONSTANT) + } else { + v.resource(Constants.UrlConstants.SAS_BLOB_CONSTANT) + } + v.startTime(startTime) .expiryTime(expiryTime) - .resource(Constants.UrlConstants.SAS_BLOB_CONSTANT) .canonicalName(String.format("/blob/%s/containerName/blobName", primaryCreds.accountName())) .snapshotId(snapId) @@ -454,6 +459,12 @@ class HelperTest extends APISpec { SASQueryParameters token = v.generateSASQueryParameters(primaryCreds) + if (startTime != null) { + expectedStringToSign = String.format(expectedStringToSign, Utility.ISO_8601_UTC_DATE_FORMATTER.format(startTime), Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiryTime)) + } else { + expectedStringToSign = String.format(expectedStringToSign, Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiryTime)) + } + then: token.signature() == primaryCreds.computeHmac256(expectedStringToSign) @@ -464,19 +475,19 @@ class HelperTest extends APISpec { Signed resource is tested elsewhere, as we work some minor magic in choosing which value to use. */ where: - permissions | startTime | expiryTime | identifier | ipRange | protocol | snapId | cacheControl | disposition | encoding | language | type || expectedStringToSign - new BlobSASPermission() | null | null | null | null | null | null | null | null | null | null | null || "r\n\n\n" + "/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null | null | null | null | null | null | null | null | null || "\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null | null | null | null | null | null | null | null || "\n\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | "id" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\nid\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | null | new IPRange() | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\nip\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | null | null | SASProtocol.HTTPS_ONLY | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n" + SASProtocol.HTTPS_ONLY + "\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | null | null | null | "snapId" | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nsnapId\n\n\n\n\n" - null | null | null | null | null | null | null | "control" | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\ncontrol\n\n\n\n" - null | null | null | null | null | null | null | null | "disposition" | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\ndisposition\n\n\n" - null | null | null | null | null | null | null | null | null | "encoding" | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\nencoding\n\n" - null | null | null | null | null | null | null | null | null | null | "language" | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\nlanguage\n" - null | null | null | null | null | null | null | null | null | null | null | "type" || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\ntype" + permissions | startTime | expiryTime | identifier | ipRange | protocol | snapId | cacheControl | disposition | encoding | language | type || expectedStringToSign + new BlobSASPermission() | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | null | null || "r\n\n%s\n" + "/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | OffsetDateTime.now(ZoneOffset.UTC).minusDays(1) | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | null | null || "\n%s\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | "id" | null | null | null | null | null | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\nid\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | new IPRange() | null | null | null | null | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\nip\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | SASProtocol.HTTPS_ONLY | null | null | null | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n" + SASProtocol.HTTPS_ONLY + "\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | "snapId" | null | null | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nsnapId\n\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | "control" | null | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\ncontrol\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | "disposition" | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\ndisposition\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | "encoding" | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\nencoding\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | "language" | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\nlanguage\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | null | "type" || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\ntype" } @Unroll @@ -489,10 +500,15 @@ class HelperTest extends APISpec { v.startTime(startTime) .expiryTime(expiryTime) - .resource(new AccountSASService().blob(true).toString()) .canonicalName(String.format("/blob/%s/containerName/blobName", primaryCreds.accountName())) .snapshotId(snapId) + if (snapId != null) { + v.resource(Constants.UrlConstants.SAS_BLOB_SNAPSHOT_CONSTANT) + } else { + v.resource(Constants.UrlConstants.SAS_BLOB_CONSTANT) + } + if (ipRange != null) { v.ipRange(new IPRange().ipMin("ip")) } @@ -546,12 +562,18 @@ class HelperTest extends APISpec { def "serviceSASSignatureValues canonicalizedResource"() { setup: ServiceSASSignatureValues v = new ServiceSASSignatureValues() - .expiryTime(OffsetDateTime.now()) + .expiryTime(expiryTime) .permissions(new BlobSASPermission().toString()) .resource(expectedResource) - .canonicalName(String.format("/blob/%s/%s/%s", primaryCreds.accountName(), containerName, blobName)) + .canonicalName(String.format("/blob/%s/%s", primaryCreds.accountName(), containerName)) .snapshotId(snapId) + if (blobName != null) { + v.canonicalName(v.canonicalName() + "/" + blobName) + } + + expectedStringToSign = String.format(expectedStringToSign, Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiryTime)) + when: SASQueryParameters token = v.generateSASQueryParameters(primaryCreds) @@ -560,10 +582,10 @@ class HelperTest extends APISpec { token.resource() == expectedResource where: - containerName | blobName | snapId || expectedResource | expectedStringToSign - "c" | "b" | "id" || "bs" | "\n\n\n" + "/blob/" + primaryCreds.accountName() + "/c/b\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nid\n\n\n\n\n" - "c" | "b" | null || "b" | "\n\n\n" + "/blob/" + primaryCreds.accountName() + "/c/b\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - "c" | null | null || "c" | "\n\n\n" + "/blob/" + primaryCreds.accountName() + "/c\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nc\n\n\n\n\n\n" + containerName | blobName | snapId | expiryTime || expectedResource | expectedStringToSign + "c" | "b" | "id" | OffsetDateTime.now() || "bs" | "\n\n%s\n" + "/blob/" + primaryCreds.accountName() + "/c/b\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nid\n\n\n\n\n" + "c" | "b" | null | OffsetDateTime.now() || "b" | "\n\n%s\n" + "/blob/" + primaryCreds.accountName() + "/c/b\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + "c" | null | null | OffsetDateTime.now() || "c" | "\n\n%s\n" + "/blob/" + primaryCreds.accountName() + "/c\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nc\n\n\n\n\n\n" } @@ -587,7 +609,6 @@ class HelperTest extends APISpec { where: containerName | version | creds | blobName || parameter - null | "v" | primaryCreds | "b" | "container" "c" | null | primaryCreds | "b" | "version" "c" | "v" | null | "b" | "sharedKeyCredentials" "c" | "v" | primaryCreds | null | "canonicalName" @@ -931,9 +952,10 @@ class HelperTest extends APISpec { .snapshot("snapshot") ServiceSASSignatureValues sasValues = new ServiceSASSignatureValues() + .expiryTime(OffsetDateTime.now(ZoneOffset.UTC).plusDays(1)) .permissions("r") .canonicalName(String.format("/blob/%s/container/blob", primaryCreds.accountName())) - .resource(Constants.UrlConstants.SAS_BLOB_CONSTANT) + .resource(Constants.UrlConstants.SAS_BLOB_SNAPSHOT_CONSTANT) parts.sasQueryParameters(sasValues.generateSASQueryParameters(primaryCreds)) @@ -946,7 +968,7 @@ class HelperTest extends APISpec { splitParts[1].contains("snapshot=snapshot") splitParts[1].contains("sp=r") splitParts[1].contains("sig=") - splitParts[1].split("&").size() == 5 // snapshot & sv & sr & sp & sig + splitParts[1].split("&").size() == 6 // snapshot & sv & sr & sp & sig & se } def "URLParser"() { diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/RequestRetryTestFactory.java b/storage/client/blob/src/test/java/com/azure/storage/blob/RequestRetryTestFactory.java index 80a1647609234..7df4714ccfef3 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/RequestRetryTestFactory.java +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/RequestRetryTestFactory.java @@ -31,7 +31,7 @@ import static java.lang.StrictMath.pow; -public class RequestRetryTestFactory { +class RequestRetryTestFactory { static final int RETRY_TEST_SCENARIO_RETRY_UNTIL_SUCCESS = 1; static final int RETRY_TEST_SCENARIO_RETRY_UNTIL_MAX_RETRIES = 2; @@ -85,7 +85,7 @@ public class RequestRetryTestFactory { private OffsetDateTime time; - public RequestRetryTestFactory(int scenario, RequestRetryOptions options) { + RequestRetryTestFactory(int scenario, RequestRetryOptions options) { this.retryTestScenario = scenario; this.options = options; } @@ -98,7 +98,7 @@ Mono send(URL url) { .send(new HttpRequest(HttpMethod.GET, url).body(Flux.just(RETRY_TEST_DEFAULT_DATA))); } - public int getTryNumber() { + int getTryNumber() { return this.tryNumber; } @@ -195,6 +195,7 @@ public Mono send(HttpRequest request) { ByteBuf buf = Unpooled.buffer(); Disposable disposable = request.body().subscribe(buf::writeBytes); while (!disposable.isDisposed()) { + // Wait until the Flux has been collected. } if (RETRY_TEST_DEFAULT_DATA.compareTo(buf) != 0) { throw new IllegalArgumentException(("Body not reset.")); diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/SASTest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/SASTest.groovy index fe40a9a45b923..a06ebeda5bd72 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/SASTest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/SASTest.groovy @@ -3,6 +3,7 @@ package com.azure.storage.blob +import com.azure.core.http.policy.HttpLogDetailLevel import com.azure.storage.blob.models.AccessPolicy import com.azure.storage.blob.models.BlobRange import com.azure.storage.blob.models.SignedIdentifier @@ -194,37 +195,40 @@ class SASTest extends APISpec { def "serviceSASSignatureValues network test container"() { setup: - def identifier = new SignedIdentifier().id("0000").accessPolicy(new AccessPolicy().permission("racwdl") - .expiry(OffsetDateTime.now().plusDays(1))) + SignedIdentifier identifier = new SignedIdentifier() + .id("0000") + .accessPolicy(new AccessPolicy().permission("racwdl") + .expiry(OffsetDateTime.now().plusDays(1))) cu.setAccessPolicy(null, Arrays.asList(identifier), null, null) // Check containerSASPermissions - def permissions = new ContainerSASPermission() + ContainerSASPermission permissions = new ContainerSASPermission() .read(true) .write(true) .create(true) .delete(true) .add(true) - def expiryTime = OffsetDateTime.now().plusDays(1) + + OffsetDateTime expiryTime = OffsetDateTime.now().plusDays(1) when: - def sasWithId = cu.generateSAS(identifier.id()) + String sasWithId = cu.generateSAS(identifier.id()) - def builder1 = new ContainerClientBuilder() - builder1.endpoint(cu.getContainerUrl().toString()) + ContainerClient client1 = new ContainerClientBuilder() + .endpoint(cu.getContainerUrl().toString()) .credential(new SASTokenCredential(sasWithId)) .httpClient(getHttpClient()) - def client1 = builder1.buildClient() + .buildClient() - client1.listBlobsFlat() + client1.listBlobsFlat().iterator().hasNext() - def sasWithPermissions = cu.generateSAS(permissions, expiryTime) + String sasWithPermissions = cu.generateSAS(permissions, expiryTime) - def builder2 = new ContainerClientBuilder() - builder2.endpoint(cu.getContainerUrl().toString()) + ContainerClient client2 = new ContainerClientBuilder() + .endpoint(cu.getContainerUrl().toString()) .credential(new SASTokenCredential(sasWithPermissions)) .httpClient(getHttpClient()) - def client2 = builder2.buildClient() + .buildClient() client2.listBlobsFlat().iterator().hasNext() @@ -293,39 +297,43 @@ class SASTest extends APISpec { BlockBlobClient bu = cu.getBlockBlobClient(blobName) bu.upload(new ByteArrayInputStream(data), data.length) String snapshotId = bu.createSnapshot().value() - def snapshotBlob = cu.getBlockBlobClient(blobName, snapshotId) + BlockBlobClient snapshotBlob = cu.getBlockBlobClient(blobName, snapshotId) - def permissions = new BlobSASPermission() + BlobSASPermission permissions = new BlobSASPermission() .read(true) .write(true) .create(true) .delete(true) .add(true) - def startTime = OffsetDateTime.now().minusDays(1) - def expiryTime = OffsetDateTime.now().plusDays(1) - def ipRange = new IPRange() + + OffsetDateTime startTime = OffsetDateTime.now().minusDays(1) + OffsetDateTime expiryTime = OffsetDateTime.now().plusDays(1) + + IPRange ipRange = new IPRange() .ipMin("0.0.0.0") .ipMax("255.255.255.255") - def sasProtocol = SASProtocol.HTTPS_HTTP - def cacheControl = "cache" - def contentDisposition = "disposition" - def contentEncoding = "encoding" - def contentLanguage = "language" - def contentType = "type" - def key = getOAuthServiceURL().getUserDelegationKey(null, OffsetDateTime.now().plusDays(1)).value() + SASProtocol sasProtocol = SASProtocol.HTTPS_HTTP + String cacheControl = "cache" + String contentDisposition = "disposition" + String contentEncoding = "encoding" + String contentLanguage = "language" + String contentType = "type" + + UserDelegationKey key = getOAuthServiceURL().getUserDelegationKey(startTime, expiryTime).value() when: - def sas = snapshotBlob.generateUserDelegationSAS(key, cu.getContainerUrl().getHost().split("\\.")[0], permissions, expiryTime, startTime, null, sasProtocol, ipRange, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType) + String sas = snapshotBlob.generateUserDelegationSAS(key, primaryCreds.accountName(), permissions, expiryTime, startTime, null, sasProtocol, ipRange, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType) // base blob with snapshot SAS - def builder1 = new BlockBlobClientBuilder() - builder1.endpoint(cu.getContainerUrl().toString()) + BlockBlobClient client1 = new BlockBlobClientBuilder() + .endpoint(cu.getContainerUrl().toString()) .blobName(blobName) .credential(new SASTokenCredential(sas)) .httpClient(getHttpClient()) - def client1 = builder1.buildClient() + .httpLogDetailLevel(HttpLogDetailLevel.BODY_AND_HEADERS) + .buildClient() client1.download(new ByteArrayOutputStream()) then: @@ -333,16 +341,17 @@ class SASTest extends APISpec { thrown(StorageException) when: - // blob snapshot with snapshot SAS - def builder2 = new BlockBlobClientBuilder() - builder2.endpoint(cu.getContainerUrl().toString()) + BlockBlobClient client2 = new BlockBlobClientBuilder() + .endpoint(cu.getContainerUrl().toString()) .blobName(blobName) .snapshot(snapshotId) .credential(new SASTokenCredential(sas)) .httpClient(getHttpClient()) - def client2 = builder2.buildClient() - def os = new ByteArrayOutputStream() + .httpLogDetailLevel(HttpLogDetailLevel.BODY_AND_HEADERS) + .buildClient() + + OutputStream os = new ByteArrayOutputStream() client2.download(os) then: @@ -361,25 +370,26 @@ class SASTest extends APISpec { def "serviceSASSignatureValues network test container user delegation"() { setup: - def permissions = new ContainerSASPermission() + ContainerSASPermission permissions = new ContainerSASPermission() .read(true) .write(true) .create(true) .delete(true) .add(true) - def expiryTime = OffsetDateTime.now().plusDays(1) - def key = getOAuthServiceURL().getUserDelegationKey(null, OffsetDateTime.now().plusDays(1)).value() + OffsetDateTime expiryTime = OffsetDateTime.now().plusDays(1) + + UserDelegationKey key = getOAuthServiceURL().getUserDelegationKey(null, OffsetDateTime.now().plusDays(1)).value() when: - def sasWithPermissions = cu.generateUserDelegationSAS(key, cu.getContainerUrl().getHost().split("\\.")[0], permissions, expiryTime) + String sasWithPermissions = cu.generateUserDelegationSAS(key, primaryCreds.accountName(), permissions, expiryTime) - def builder = new ContainerClientBuilder() - builder.endpoint(cu.getContainerUrl().toString()) + ContainerClient client = new ContainerClientBuilder() + .endpoint(cu.getContainerUrl().toString()) .credential(new SASTokenCredential(sasWithPermissions)) .httpClient(getHttpClient()) - def client = builder.buildClient() + .buildClient() client.listBlobsFlat().iterator().hasNext() From f0712aa95737298f59813c14edd08e392b29de84 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Fri, 19 Jul 2019 12:25:02 -0700 Subject: [PATCH 14/19] Fix Checkstyle issue --- .../java/com/azure/storage/blob/RequestRetryTestFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/RequestRetryTestFactory.java b/storage/client/blob/src/test/java/com/azure/storage/blob/RequestRetryTestFactory.java index 7df4714ccfef3..63e289a052c9f 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/RequestRetryTestFactory.java +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/RequestRetryTestFactory.java @@ -195,7 +195,7 @@ public Mono send(HttpRequest request) { ByteBuf buf = Unpooled.buffer(); Disposable disposable = request.body().subscribe(buf::writeBytes); while (!disposable.isDisposed()) { - // Wait until the Flux has been collected. + System.out.println("Waiting for Flux to finish to prevent blocking on another thread exception"); } if (RETRY_TEST_DEFAULT_DATA.compareTo(buf) != 0) { throw new IllegalArgumentException(("Body not reset.")); From 4081bdc05166a6efbd20f4208b06f1302b5219df Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Fri, 19 Jul 2019 12:43:19 -0700 Subject: [PATCH 15/19] Move adding acount name into String.format --- .../com/azure/storage/blob/HelperTest.groovy | 79 +++++++++++-------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy index 661449c85c7bf..15a45da47fe6c 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy @@ -460,9 +460,14 @@ class HelperTest extends APISpec { SASQueryParameters token = v.generateSASQueryParameters(primaryCreds) if (startTime != null) { - expectedStringToSign = String.format(expectedStringToSign, Utility.ISO_8601_UTC_DATE_FORMATTER.format(startTime), Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiryTime)) + expectedStringToSign = String.format(expectedStringToSign, + Utility.ISO_8601_UTC_DATE_FORMATTER.format(startTime), + Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiryTime), + primaryCreds.accountName()) } else { - expectedStringToSign = String.format(expectedStringToSign, Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiryTime)) + expectedStringToSign = String.format(expectedStringToSign, + Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiryTime), + primaryCreds.accountName()) } then: @@ -476,18 +481,18 @@ class HelperTest extends APISpec { */ where: permissions | startTime | expiryTime | identifier | ipRange | protocol | snapId | cacheControl | disposition | encoding | language | type || expectedStringToSign - new BlobSASPermission() | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | null | null || "r\n\n%s\n" + "/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | OffsetDateTime.now(ZoneOffset.UTC).minusDays(1) | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | null | null || "\n%s\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | "id" | null | null | null | null | null | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\nid\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | new IPRange() | null | null | null | null | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\nip\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | SASProtocol.HTTPS_ONLY | null | null | null | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n" + SASProtocol.HTTPS_ONLY + "\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | "snapId" | null | null | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nsnapId\n\n\n\n\n" - null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | "control" | null | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\ncontrol\n\n\n\n" - null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | "disposition" | null | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\ndisposition\n\n\n" - null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | "encoding" | null | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\nencoding\n\n" - null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | "language" | null || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\nlanguage\n" - null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | null | "type" || "\n\n%s\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\ntype" + new BlobSASPermission() | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | null | null || "r\n\n%s\n" + "/blob/%s/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | OffsetDateTime.now(ZoneOffset.UTC).minusDays(1) | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | null | null || "\n%s\n%s\n/blob/%s/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | null | null || "\n\n%s\n/blob/%s/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | "id" | null | null | null | null | null | null | null | null || "\n\n%s\n/blob/%s/containerName/blobName\nid\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | new IPRange() | null | null | null | null | null | null | null || "\n\n%s\n/blob/%s/containerName/blobName\n\nip\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | SASProtocol.HTTPS_ONLY | null | null | null | null | null | null || "\n\n%s\n/blob/%s/containerName/blobName\n\n\n" + SASProtocol.HTTPS_ONLY + "\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | "snapId" | null | null | null | null | null || "\n\n%s\n/blob/%s/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nsnapId\n\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | "control" | null | null | null | null || "\n\n%s\n/blob/%s/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\ncontrol\n\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | "disposition" | null | null | null || "\n\n%s\n/blob/%s/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\ndisposition\n\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | "encoding" | null | null || "\n\n%s\n/blob/%s/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\nencoding\n\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | "language" | null || "\n\n%s\n/blob/%s/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\nlanguage\n" + null | null | OffsetDateTime.now(ZoneOffset.UTC).plusDays(1) | null | null | null | null | null | null | null | null | "type" || "\n\n%s\n/blob/%s/containerName/blobName\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\ntype" } @Unroll @@ -531,6 +536,8 @@ class HelperTest extends APISpec { SASQueryParameters token = v.generateSASQueryParameters(key) + expectedStringToSign = String.format(expectedStringToSign, primaryCreds.accountName()) + then: token.signature() == Utility.delegateComputeHmac256(key, expectedStringToSign) @@ -539,23 +546,23 @@ class HelperTest extends APISpec { */ where: permissions | startTime | expiryTime | keyOid | keyTid | keyStart | keyExpiry | keyService | keyVersion | keyValue | ipRange | protocol | snapId | cacheControl | disposition | encoding | language | type || expectedStringToSign - new BlobSASPermission() | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "r\n\n\n" + "/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | "11111111-1111-1111-1111-111111111111" | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n11111111-1111-1111-1111-111111111111\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | null | "22222222-2222-2222-2222-222222222222" | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n22222222-2222-2222-2222-222222222222\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | null | null | OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC) | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n2018-01-01T00:00:00Z\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | null | null | null | OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC) | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n2018-01-01T00:00:00Z\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | null | null | null | null | "b" | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\nb\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | null | null | null | null | null | "2018-06-17" | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n2018-06-17\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | new IPRange() | null | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\nip\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | SASProtocol.HTTPS_ONLY | null | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n" + SASProtocol.HTTPS_ONLY + "\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | "snapId" | null | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nsnapId\n\n\n\n\n" - null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | "control" | null | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\ncontrol\n\n\n\n" - null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | "disposition" | null | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\ndisposition\n\n\n" - null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | "encoding" | null | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\nencoding\n\n" - null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | "language" | null || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\nlanguage\n" - null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | "type" || "\n\n\n/blob/" + primaryCreds.accountName() + "/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\ntype" + new BlobSASPermission() | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "r\n\n\n" + "/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | "11111111-1111-1111-1111-111111111111" | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/%s/containerName/blobName\n11111111-1111-1111-1111-111111111111\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | "22222222-2222-2222-2222-222222222222" | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/%s/containerName/blobName\n\n22222222-2222-2222-2222-222222222222\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC) | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/%s/containerName/blobName\n\n\n2018-01-01T00:00:00Z\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC) | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/%s/containerName/blobName\n\n\n\n2018-01-01T00:00:00Z\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | null | "b" | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/%s/containerName/blobName\n\n\n\n\nb\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | null | null | "2018-06-17" | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | null || "\n\n\n/blob/%s/containerName/blobName\n\n\n\n\n\n2018-06-17\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | new IPRange() | null | null | null | null | null | null | null || "\n\n\n/blob/%s/containerName/blobName\n\n\n\n\n\n\nip\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | SASProtocol.HTTPS_ONLY | null | null | null | null | null | null || "\n\n\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n" + SASProtocol.HTTPS_ONLY + "\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | "snapId" | null | null | null | null | null || "\n\n\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nsnapId\n\n\n\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | "control" | null | null | null | null || "\n\n\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\ncontrol\n\n\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | "disposition" | null | null | null || "\n\n\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\ndisposition\n\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | "encoding" | null | null || "\n\n\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\nencoding\n\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | "language" | null || "\n\n\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\nlanguage\n" + null | null | null | null | null | null | null | null | null | "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=" | null | null | null | null | null | null | null | "type" || "\n\n\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\ntype" } @Unroll @@ -572,7 +579,9 @@ class HelperTest extends APISpec { v.canonicalName(v.canonicalName() + "/" + blobName) } - expectedStringToSign = String.format(expectedStringToSign, Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiryTime)) + expectedStringToSign = String.format(expectedStringToSign, + Utility.ISO_8601_UTC_DATE_FORMATTER.format(expiryTime), + primaryCreds.accountName()) when: SASQueryParameters token = v.generateSASQueryParameters(primaryCreds) @@ -583,9 +592,9 @@ class HelperTest extends APISpec { where: containerName | blobName | snapId | expiryTime || expectedResource | expectedStringToSign - "c" | "b" | "id" | OffsetDateTime.now() || "bs" | "\n\n%s\n" + "/blob/" + primaryCreds.accountName() + "/c/b\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nid\n\n\n\n\n" - "c" | "b" | null | OffsetDateTime.now() || "b" | "\n\n%s\n" + "/blob/" + primaryCreds.accountName() + "/c/b\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" - "c" | null | null | OffsetDateTime.now() || "c" | "\n\n%s\n" + "/blob/" + primaryCreds.accountName() + "/c\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nc\n\n\n\n\n\n" + "c" | "b" | "id" | OffsetDateTime.now() || "bs" | "\n\n%s\n" + "/blob/%s/c/b\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nbs\nid\n\n\n\n\n" + "c" | "b" | null | OffsetDateTime.now() || "b" | "\n\n%s\n" + "/blob/%s/c/b\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nb\n\n\n\n\n\n" + "c" | null | null | OffsetDateTime.now() || "c" | "\n\n%s\n" + "/blob/%s/c\n\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\nc\n\n\n\n\n\n" } From 74dfdfc13a9868dd790e5468f4f7284d92ca2b1f Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Fri, 19 Jul 2019 13:04:33 -0700 Subject: [PATCH 16/19] Additional move of adding account name into String.format --- .../test/java/com/azure/storage/blob/HelperTest.groovy | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy index 15a45da47fe6c..502e628a597e7 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/HelperTest.groovy @@ -802,14 +802,16 @@ class HelperTest extends APISpec { def token = v.generateSASQueryParameters(primaryCreds) + expectedStringToSign = String.format(expectedStringToSign, primaryCreds.accountName()) + then: token.signature() == primaryCreds.computeHmac256(expectedStringToSign) where: startTime | ipRange | protocol || expectedStringToSign - OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null || primaryCreds.accountName() + "\nr\nb\no\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\n" - null | new IPRange() | null || primaryCreds.accountName() + "\nr\nb\no\n\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\nip\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\n" - null | null | SASProtocol.HTTPS_ONLY || primaryCreds.accountName() + "\nr\nb\no\n\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n\n" + SASProtocol.HTTPS_ONLY + "\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\n" + OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) | null | null || "%s\nr\nb\no\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\n" + null | new IPRange() | null || "%s\nr\nb\no\n\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\nip\n\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\n" + null | null | SASProtocol.HTTPS_ONLY || "%s\nr\nb\no\n\n" + Utility.ISO_8601_UTC_DATE_FORMATTER.format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n\n" + SASProtocol.HTTPS_ONLY + "\n" + Constants.HeaderConstants.TARGET_STORAGE_VERSION + "\n" } @Unroll From 008dadde9caf1485912ef547568e879d90e506a4 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Mon, 22 Jul 2019 10:18:50 -0700 Subject: [PATCH 17/19] Fixed assertGenerateOK logic for SAS generation --- .../blob/ServiceSASSignatureValues.java | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java index e82efd73741cb..448ac9d3a854d 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java @@ -466,33 +466,15 @@ private void assertGenerateOK(boolean usingUserDelegation) { Utility.assertNotNull("version", this.version); Utility.assertNotNull("canonicalName", this.canonicalName); - // If a SignedIdentifier or UserDelegationKey isn't being used expiryTime and permissions must be set. - if (!usingUserDelegation && this.identifier == null) { + // If a UserDelegation key or a SignedIdentifier is not being used both expiryDate and permissions must be set. + if (usingUserDelegation || identifier == null) { Utility.assertNotNull("expiryTime", this.expiryTime); Utility.assertNotNull("permissions", this.permissions); + } else if (!usingUserDelegation) { + // Otherwise a SignedIdentifier must be used. + Utility.assertNotNull("identifier", this.identifier); } - // When using an identifier or delegation key permissions and expiry time are allowed to be null. -// if (this.expiryTime == null && this.permissions == null) { -// if (!usingUserDelegation) { -// Utility.assertNotNull("identifier", this.identifier); -// } -// } else { -// Utility.assertNotNull("expiryTime", this.expiryTime); -// Utility.assertNotNull("permissions", this.permissions); -// } - -// // Ensure either (expiryTime and permissions) or (identifier) is set -// if (this.identifier == null && (this.expiryTime == null && this.permissions == null)) { -// // Identifier is not required if user delegation is being used -// if (!usingUserDelegation) { -// Utility.assertNotNull("identifier", this.identifier); -// } -// } else { -// Utility.assertNotNull("expiryTime", this.expiryTime); -// Utility.assertNotNull("permissions", this.permissions); -// } - if (Constants.UrlConstants.SAS_CONTAINER_CONSTANT.equals(this.resource) && this.snapshotId != null) { throw new IllegalArgumentException("Cannot set a snapshotId without resource being a blob."); } From 0af192c4b8724ceb3043d61c62480a692c2d2ae1 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Tue, 30 Jul 2019 17:01:23 -0700 Subject: [PATCH 18/19] Removed invalid import --- .../java/com/azure/storage/blob/ServiceSASSignatureValues.java | 1 - 1 file changed, 1 deletion(-) diff --git a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java index 5e2187cfa37b1..448ac9d3a854d 100644 --- a/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java +++ b/storage/client/blob/src/main/java/com/azure/storage/blob/ServiceSASSignatureValues.java @@ -3,7 +3,6 @@ package com.azure.storage.blob; -import com.azure.core.annotations.HEAD; import com.azure.storage.blob.models.UserDelegationKey; import com.azure.storage.common.credentials.SharedKeyCredential; From a48197b32eab03020641e737532bc171ca0185ba Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Wed, 31 Jul 2019 11:36:31 -0700 Subject: [PATCH 19/19] Fixed method parameter ordering --- .../src/test/java/com/azure/storage/blob/BlobAPITest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/client/blob/src/test/java/com/azure/storage/blob/BlobAPITest.groovy b/storage/client/blob/src/test/java/com/azure/storage/blob/BlobAPITest.groovy index 03e2bacf3ede4..ec294bee15419 100644 --- a/storage/client/blob/src/test/java/com/azure/storage/blob/BlobAPITest.groovy +++ b/storage/client/blob/src/test/java/com/azure/storage/blob/BlobAPITest.groovy @@ -115,7 +115,7 @@ class BlobAPITest extends APISpec { when: BlobRange range = new BlobRange(2, 5L) ReliableDownloadOptions options = new ReliableDownloadOptions().maxRetryRequests(3) - bu2.download(new ByteArrayOutputStream(), options, range, null, false, null) + bu2.download(new ByteArrayOutputStream(), range, options, null, false, null) then: /*