AppendBlobClient.java
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.storage.blob.specialized;
import com.azure.core.annotation.ServiceClient;
import com.azure.core.exception.UnexpectedLengthException;
import com.azure.core.http.rest.Response;
import com.azure.core.util.Context;
import com.azure.storage.blob.BlobClient;
import com.azure.storage.blob.BlobClientBuilder;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.options.AppendBlobCreateOptions;
import com.azure.storage.blob.models.AppendBlobItem;
import com.azure.storage.blob.models.AppendBlobRequestConditions;
import com.azure.storage.blob.models.BlobHttpHeaders;
import com.azure.storage.blob.models.BlobRange;
import com.azure.storage.blob.models.BlobRequestConditions;
import com.azure.storage.blob.models.BlobStorageException;
import com.azure.storage.blob.options.AppendBlobSealOptions;
import com.azure.storage.common.Utility;
import com.azure.storage.common.implementation.Constants;
import com.azure.storage.common.implementation.StorageImplUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import static com.azure.storage.common.implementation.StorageImplUtils.blockWithOptionalTimeout;
/**
* Client to an append blob. It may only be instantiated through a {@link SpecializedBlobClientBuilder} or via the
* method {@link BlobClient#getAppendBlobClient()}. 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.
*
* <p>
* This client contains operations on a blob. Operations on a container are available on {@link BlobContainerClient},
* and operations on the service are available on {@link BlobServiceClient}.
*
* <p>
* Please refer to the <a href=https://docs.microsoft.com/en-us/rest/api/storageservices/understanding-block-blobs--append-blobs--and-page-blobs>Azure
* Docs</a> for more information.
*/
@ServiceClient(builder = SpecializedBlobClientBuilder.class)
public final class AppendBlobClient extends BlobClientBase {
private final 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 = AppendBlobAsyncClient.MAX_APPEND_BLOCK_BYTES;
/**
* Indicates the maximum number of blocks allowed in an append blob.
*/
public static final int MAX_BLOCKS = AppendBlobAsyncClient.MAX_BLOCKS;
/**
* Package-private constructor for use by {@link BlobClientBuilder}.
*
* @param appendBlobAsyncClient the async append blob client
*/
AppendBlobClient(AppendBlobAsyncClient appendBlobAsyncClient) {
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.
*
* @return A {@link BlobOutputStream} object used to write data to the blob.
* @throws BlobStorageException If a storage service error occurred.
*/
public BlobOutputStream getBlobOutputStream() {
return getBlobOutputStream(null);
}
/**
* 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.
*
* @param requestConditions A {@link BlobRequestConditions} object that represents the access conditions for the
* blob.
* @return A {@link BlobOutputStream} object used to write data to the blob.
* @throws BlobStorageException If a storage service error occurred.
*/
public BlobOutputStream getBlobOutputStream(AppendBlobRequestConditions requestConditions) {
return BlobOutputStream.appendBlobOutputStream(appendBlobAsyncClient, requestConditions);
}
/**
* Creates a 0-length append blob. Call appendBlock to append data to an append blob. By default this method will
* not overwrite an existing blob.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.create}
*
* @return The information of the created appended blob.
*/
public AppendBlobItem create() {
return create(false);
}
/**
* Creates a 0-length append blob. Call appendBlock to append data to an append blob.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.create#boolean}
*
* @param overwrite Whether or not to overwrite, should data exist on the blob.
*
* @return The information of the created appended blob.
*/
public AppendBlobItem create(boolean overwrite) {
BlobRequestConditions blobRequestConditions = new BlobRequestConditions();
if (!overwrite) {
blobRequestConditions.setIfNoneMatch(Constants.HeaderConstants.ETAG_WILDCARD);
}
return createWithResponse(null, null, blobRequestConditions, null, Context.NONE).getValue();
}
/**
* Creates a 0-length append blob. Call appendBlock to append data to an append blob.
* <p>
* To avoid overwriting, pass "*" to {@link BlobRequestConditions#setIfNoneMatch(String)}.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.createWithResponse#BlobHttpHeaders-Map-BlobRequestConditions-Duration-Context}
*
* @param headers {@link BlobHttpHeaders}
* @param metadata Metadata to associate with the blob. If there is leading or trailing whitespace in any
* metadata key or value, it must be removed or encoded.
* @param requestConditions {@link BlobRequestConditions}
* @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised.
* @param context Additional context that is passed through the Http pipeline during the service call.
* @return A {@link Response} whose {@link Response#getValue() value} contains the created appended blob.
*/
public Response<AppendBlobItem> createWithResponse(BlobHttpHeaders headers, Map<String, String> metadata,
BlobRequestConditions requestConditions, Duration timeout, Context context) {
return this.createWithResponse(new AppendBlobCreateOptions().setHeaders(headers).setMetadata(metadata)
.setRequestConditions(requestConditions), timeout, context);
}
/**
* Creates a 0-length append blob. Call appendBlock to append data to an append blob.
* <p>
* To avoid overwriting, pass "*" to {@link BlobRequestConditions#setIfNoneMatch(String)}.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.createWithResponse#AppendBlobCreateOptions-Duration-Context}
*
* @param options {@link AppendBlobCreateOptions}
* @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised.
* @param context Additional context that is passed through the Http pipeline during the service call.
* @return A {@link Response} whose {@link Response#getValue() value} contains the created appended blob.
*/
public Response<AppendBlobItem> createWithResponse(AppendBlobCreateOptions options, Duration timeout,
Context context) {
return StorageImplUtils.blockWithOptionalTimeout(appendBlobAsyncClient.
createWithResponse(options, context), timeout);
}
/**
* Commits a new block of data to the end of the existing append blob.
* <p>
* 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.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.appendBlock#InputStream-long}
*
* @param data The data to write to the blob. The data must be markable. This is in order to support retries. If
* the data is not markable, consider using {@link #getBlobOutputStream()} and writing to the returned OutputStream.
* Alternatively, consider wrapping your data source in a {@link java.io.BufferedInputStream} to add mark support.
* @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 The information of the append blob operation.
*/
public AppendBlobItem appendBlock(InputStream data, long length) {
return appendBlockWithResponse(data, length, null, null, null, Context.NONE).getValue();
}
/**
* Commits a new block of data to the end of the existing append blob.
* <p>
* 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.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.appendBlockWithResponse#InputStream-long-byte-AppendBlobRequestConditions-Duration-Context}
*
* @param data The data to write to the blob. The data must be markable. This is in order to support retries. If
* the data is not markable, consider using {@link #getBlobOutputStream()} and writing to the returned OutputStream.
* Alternatively, consider wrapping your data source in a {@link java.io.BufferedInputStream} to add mark support.
* @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 contentMd5 An MD5 hash of the block content. This hash is used to verify the integrity of the block during
* transport. When this header is specified, the storage service compares the hash of the content that has arrived
* with this header value. Note that this MD5 hash is not stored with the blob. If the two hashes do not match, the
* operation will fail.
* @param appendBlobRequestConditions {@link AppendBlobRequestConditions}
* @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised.
* @param context Additional context that is passed through the Http pipeline during the service call.
* @return A {@link Response} whose {@link Response#getValue() value} contains the append blob operation.
* @throws UnexpectedLengthException when the length of data does not match the input {@code length}.
* @throws NullPointerException if the input data is null.
*/
public Response<AppendBlobItem> appendBlockWithResponse(InputStream data, long length, byte[] contentMd5,
AppendBlobRequestConditions appendBlobRequestConditions, Duration timeout, Context context) {
Objects.requireNonNull(data, "'data' cannot be null.");
Flux<ByteBuffer> fbb = Utility.convertStreamToByteBuffer(data, length, MAX_APPEND_BLOCK_BYTES, true);
Mono<Response<AppendBlobItem>> response = appendBlobAsyncClient.appendBlockWithResponse(
fbb.subscribeOn(Schedulers.elastic()), length, contentMd5, appendBlobRequestConditions, context);
return StorageImplUtils.blockWithOptionalTimeout(response, timeout);
}
/**
* Commits a new block of data from another blob to the end of this append blob.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.appendBlockFromUrl#String-BlobRange}
*
* @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 The information of the append blob operation.
*/
public AppendBlobItem appendBlockFromUrl(String sourceUrl, BlobRange sourceRange) {
return appendBlockFromUrlWithResponse(sourceUrl, sourceRange, null, null, null, null, Context.NONE).getValue();
}
/**
* Commits a new block of data from another blob to the end of this append blob.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.appendBlockFromUrlWithResponse#String-BlobRange-byte-AppendBlobRequestConditions-BlobRequestConditions-Duration-Context}
*
* @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 destRequestConditions {@link AppendBlobRequestConditions}
* @param sourceRequestConditions {@link BlobRequestConditions}
* @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised.
* @param context Additional context that is passed through the Http pipeline during the service call.
* @return The information of the append blob operation.
*/
public Response<AppendBlobItem> appendBlockFromUrlWithResponse(String sourceUrl, BlobRange sourceRange,
byte[] sourceContentMd5, AppendBlobRequestConditions destRequestConditions,
BlobRequestConditions sourceRequestConditions, Duration timeout, Context context) {
Mono<Response<AppendBlobItem>> response = appendBlobAsyncClient.appendBlockFromUrlWithResponse(sourceUrl,
sourceRange, sourceContentMd5, destRequestConditions, sourceRequestConditions, context);
return StorageImplUtils.blockWithOptionalTimeout(response, timeout);
}
/**
* Seals an append blob, making it read only. Any subsequent appends will fail.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.seal}
*/
public void seal() {
sealWithResponse(new AppendBlobSealOptions(), null, Context.NONE);
}
/**
* Seals an append blob, making it read only. Any subsequent appends will fail.
*
* <p><strong>Code Samples</strong></p>
*
* {@codesnippet com.azure.storage.blob.specialized.AppendBlobClient.sealWithResponse#AppendBlobSealOptions-Duration-Context}
*
* @param options {@link AppendBlobSealOptions}
* @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised.
* @param context Additional context that is passed through the Http pipeline during the service call.
* @return A reactive response signalling completion.
*/
public Response<Void> sealWithResponse(AppendBlobSealOptions options, Duration timeout, Context context) {
Mono<Response<Void>> response = appendBlobAsyncClient.sealWithResponse(options, context);
return blockWithOptionalTimeout(response, timeout);
}
}