software.amazon.awssdk.transfer.s3.internal.GenericS3TransferManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of s3-transfer-manager Show documentation
Show all versions of s3-transfer-manager Show documentation
The S3 Transfer Manager allows customers to easily and optimally
transfer objects and directories to and from S3.
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package software.amazon.awssdk.transfer.s3.internal;
import static software.amazon.awssdk.services.s3.internal.multipart.MultipartDownloadUtils.multipartDownloadResumeContext;
import static software.amazon.awssdk.services.s3.multipart.S3MultipartExecutionAttribute.JAVA_PROGRESS_LISTENER;
import static software.amazon.awssdk.services.s3.multipart.S3MultipartExecutionAttribute.MULTIPART_DOWNLOAD_RESUME_CONTEXT;
import static software.amazon.awssdk.services.s3.multipart.S3MultipartExecutionAttribute.PAUSE_OBSERVABLE;
import static software.amazon.awssdk.services.s3.multipart.S3MultipartExecutionAttribute.RESUME_TOKEN;
import static software.amazon.awssdk.transfer.s3.SizeConstant.MB;
import static software.amazon.awssdk.transfer.s3.internal.utils.ResumableRequestConverter.toDownloadFileRequestAndTransformer;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Consumer;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
import software.amazon.awssdk.arns.Arn;
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
import software.amazon.awssdk.core.FileTransformerConfiguration;
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.exception.SdkException;
import software.amazon.awssdk.core.internal.async.FileAsyncRequestBody;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.internal.multipart.MultipartDownloadResumeContext;
import software.amazon.awssdk.services.s3.internal.multipart.MultipartS3AsyncClient;
import software.amazon.awssdk.services.s3.internal.resource.S3AccessPointResource;
import software.amazon.awssdk.services.s3.internal.resource.S3ArnConverter;
import software.amazon.awssdk.services.s3.internal.resource.S3Resource;
import software.amazon.awssdk.services.s3.model.CopyObjectRequest;
import software.amazon.awssdk.services.s3.model.CopyObjectResponse;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import software.amazon.awssdk.services.s3.multipart.PauseObservable;
import software.amazon.awssdk.services.s3.multipart.S3ResumeToken;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.internal.model.DefaultCopy;
import software.amazon.awssdk.transfer.s3.internal.model.DefaultDirectoryDownload;
import software.amazon.awssdk.transfer.s3.internal.model.DefaultDirectoryUpload;
import software.amazon.awssdk.transfer.s3.internal.model.DefaultDownload;
import software.amazon.awssdk.transfer.s3.internal.model.DefaultFileDownload;
import software.amazon.awssdk.transfer.s3.internal.model.DefaultFileUpload;
import software.amazon.awssdk.transfer.s3.internal.model.DefaultUpload;
import software.amazon.awssdk.transfer.s3.internal.progress.DefaultTransferProgress;
import software.amazon.awssdk.transfer.s3.internal.progress.DefaultTransferProgressSnapshot;
import software.amazon.awssdk.transfer.s3.internal.progress.ResumeTransferProgress;
import software.amazon.awssdk.transfer.s3.internal.progress.TransferProgressUpdater;
import software.amazon.awssdk.transfer.s3.model.CompletedCopy;
import software.amazon.awssdk.transfer.s3.model.CompletedDownload;
import software.amazon.awssdk.transfer.s3.model.CompletedFileDownload;
import software.amazon.awssdk.transfer.s3.model.CompletedFileUpload;
import software.amazon.awssdk.transfer.s3.model.CompletedUpload;
import software.amazon.awssdk.transfer.s3.model.Copy;
import software.amazon.awssdk.transfer.s3.model.CopyRequest;
import software.amazon.awssdk.transfer.s3.model.DirectoryDownload;
import software.amazon.awssdk.transfer.s3.model.DirectoryUpload;
import software.amazon.awssdk.transfer.s3.model.Download;
import software.amazon.awssdk.transfer.s3.model.DownloadDirectoryRequest;
import software.amazon.awssdk.transfer.s3.model.DownloadFileRequest;
import software.amazon.awssdk.transfer.s3.model.DownloadRequest;
import software.amazon.awssdk.transfer.s3.model.FileDownload;
import software.amazon.awssdk.transfer.s3.model.FileUpload;
import software.amazon.awssdk.transfer.s3.model.ResumableFileDownload;
import software.amazon.awssdk.transfer.s3.model.ResumableFileUpload;
import software.amazon.awssdk.transfer.s3.model.Upload;
import software.amazon.awssdk.transfer.s3.model.UploadDirectoryRequest;
import software.amazon.awssdk.transfer.s3.model.UploadFileRequest;
import software.amazon.awssdk.transfer.s3.model.UploadRequest;
import software.amazon.awssdk.transfer.s3.progress.TransferProgress;
import software.amazon.awssdk.utils.CompletableFutureUtils;
import software.amazon.awssdk.utils.IoUtils;
import software.amazon.awssdk.utils.Logger;
import software.amazon.awssdk.utils.Pair;
import software.amazon.awssdk.utils.Validate;
@SdkInternalApi
class GenericS3TransferManager implements S3TransferManager {
protected static final int DEFAULT_FILE_UPLOAD_CHUNK_SIZE = (int) (16 * MB);
private static final Logger log = Logger.loggerFor(S3TransferManager.class);
private static final PauseResumeHelper PAUSE_RESUME_HELPER = new PauseResumeHelper();
private final S3AsyncClient s3AsyncClient;
private final UploadDirectoryHelper uploadDirectoryHelper;
private final DownloadDirectoryHelper downloadDirectoryHelper;
private final boolean isDefaultS3AsyncClient;
private final TransferManagerConfiguration transferConfiguration;
GenericS3TransferManager(TransferManagerConfiguration transferConfiguration,
S3AsyncClient s3AsyncClient,
boolean isDefaultS3AsyncClient) {
this.s3AsyncClient = s3AsyncClient;
this.transferConfiguration = transferConfiguration;
uploadDirectoryHelper = new UploadDirectoryHelper(transferConfiguration, this::uploadFile);
ListObjectsHelper listObjectsHelper = new ListObjectsHelper(s3AsyncClient::listObjectsV2);
downloadDirectoryHelper = new DownloadDirectoryHelper(transferConfiguration,
listObjectsHelper,
this::downloadFile);
this.isDefaultS3AsyncClient = isDefaultS3AsyncClient;
}
@SdkTestInternalApi
GenericS3TransferManager(S3AsyncClient s3AsyncClient,
UploadDirectoryHelper uploadDirectoryHelper,
TransferManagerConfiguration configuration,
DownloadDirectoryHelper downloadDirectoryHelper) {
this.s3AsyncClient = s3AsyncClient;
this.isDefaultS3AsyncClient = false;
this.transferConfiguration = configuration;
this.uploadDirectoryHelper = uploadDirectoryHelper;
this.downloadDirectoryHelper = downloadDirectoryHelper;
}
@Override
public final Upload upload(UploadRequest uploadRequest) {
Validate.paramNotNull(uploadRequest, "uploadRequest");
AsyncRequestBody requestBody = uploadRequest.requestBody();
CompletableFuture returnFuture = new CompletableFuture<>();
TransferProgressUpdater progressUpdater = new TransferProgressUpdater(uploadRequest,
requestBody.contentLength().orElse(null));
progressUpdater.transferInitiated();
requestBody = progressUpdater.wrapRequestBody(requestBody);
progressUpdater.registerCompletion(returnFuture);
PutObjectRequest putObjectRequest = uploadRequest.putObjectRequest();
if (isS3ClientMultipartEnabled()) {
Consumer attachProgressListener =
b -> b.putExecutionAttribute(JAVA_PROGRESS_LISTENER, progressUpdater.multipartClientProgressListener());
putObjectRequest = attachSdkAttribute(uploadRequest.putObjectRequest(), attachProgressListener);
}
try {
assertNotUnsupportedArn(uploadRequest.putObjectRequest().bucket(), "upload");
CompletableFuture future =
s3AsyncClient.putObject(putObjectRequest, requestBody);
// Forward upload cancellation to future
CompletableFutureUtils.forwardExceptionTo(returnFuture, future);
CompletableFutureUtils.forwardTransformedResultTo(future, returnFuture,
r -> CompletedUpload.builder()
.response(r)
.build());
} catch (Throwable throwable) {
returnFuture.completeExceptionally(throwable);
}
return new DefaultUpload(returnFuture, progressUpdater.progress());
}
/**
* May be overridden by subclasses to provide customized behavior
*/
@Override
public FileUpload uploadFile(UploadFileRequest uploadFileRequest) {
Validate.paramNotNull(uploadFileRequest, "uploadFileRequest");
AsyncRequestBody requestBody =
FileAsyncRequestBody.builder()
.path(uploadFileRequest.source())
.chunkSizeInBytes(DEFAULT_FILE_UPLOAD_CHUNK_SIZE)
.build();
CompletableFuture returnFuture = new CompletableFuture<>();
TransferProgressUpdater progressUpdater = new TransferProgressUpdater(uploadFileRequest,
requestBody.contentLength().orElse(null));
progressUpdater.transferInitiated();
requestBody = progressUpdater.wrapRequestBody(requestBody);
progressUpdater.registerCompletion(returnFuture);
PutObjectRequest putObjectRequest = uploadFileRequest.putObjectRequest();
PauseObservable pauseObservable;
if (isS3ClientMultipartEnabled()) {
pauseObservable = new PauseObservable();
Consumer attachObservableAndListener =
b -> b.putExecutionAttribute(PAUSE_OBSERVABLE, pauseObservable)
.putExecutionAttribute(JAVA_PROGRESS_LISTENER, progressUpdater.multipartClientProgressListener());
putObjectRequest = attachSdkAttribute(uploadFileRequest.putObjectRequest(), attachObservableAndListener);
} else {
pauseObservable = null;
}
try {
assertNotUnsupportedArn(putObjectRequest.bucket(), "upload");
CompletableFuture putObjectFuture =
s3AsyncClient.putObject(putObjectRequest, requestBody);
// Forward upload cancellation to putObjectFuture
CompletableFutureUtils.forwardExceptionTo(returnFuture, putObjectFuture);
CompletableFutureUtils.forwardTransformedResultTo(putObjectFuture, returnFuture,
r -> CompletedFileUpload.builder()
.response(r)
.build());
} catch (Throwable throwable) {
returnFuture.completeExceptionally(throwable);
}
return new DefaultFileUpload(returnFuture, progressUpdater.progress(), pauseObservable, uploadFileRequest);
}
@Override
public final FileUpload resumeUploadFile(ResumableFileUpload resumableFileUpload) {
Validate.paramNotNull(resumableFileUpload, "resumableFileUpload");
boolean fileModified = PAUSE_RESUME_HELPER.fileModified(resumableFileUpload, s3AsyncClient);
boolean noResumeToken = !PAUSE_RESUME_HELPER.hasResumeToken(resumableFileUpload);
if (fileModified || noResumeToken) {
return uploadFile(resumableFileUpload.uploadFileRequest());
}
return doResumeUpload(resumableFileUpload);
}
private boolean isS3ClientMultipartEnabled() {
// TODO use configuration getter when available
return s3AsyncClient instanceof MultipartS3AsyncClient;
}
/**
* Can be overridden by subclasses to provide different implementation
*/
FileUpload doResumeUpload(ResumableFileUpload resumableFileUpload) {
UploadFileRequest uploadFileRequest = resumableFileUpload.uploadFileRequest();
PutObjectRequest putObjectRequest = uploadFileRequest.putObjectRequest();
S3ResumeToken s3ResumeToken = s3ResumeToken(resumableFileUpload);
Consumer attachResumeToken =
b -> b.putExecutionAttribute(RESUME_TOKEN, s3ResumeToken);
PutObjectRequest modifiedPutObjectRequest = attachSdkAttribute(putObjectRequest, attachResumeToken);
return uploadFile(uploadFileRequest.toBuilder()
.putObjectRequest(modifiedPutObjectRequest)
.build());
}
private static S3ResumeToken s3ResumeToken(ResumableFileUpload resumableFileUpload) {
S3ResumeToken.Builder builder = S3ResumeToken.builder();
builder.uploadId(resumableFileUpload.multipartUploadId().orElse(null));
if (resumableFileUpload.partSizeInBytes().isPresent()) {
builder.partSize(resumableFileUpload.partSizeInBytes().getAsLong());
}
if (resumableFileUpload.totalParts().isPresent()) {
builder.totalNumParts(resumableFileUpload.totalParts().getAsLong());
}
if (resumableFileUpload.transferredParts().isPresent()) {
builder.numPartsCompleted(resumableFileUpload.transferredParts().getAsLong());
}
return builder.build();
}
private PutObjectRequest attachSdkAttribute(PutObjectRequest putObjectRequest,
Consumer builderMutation) {
AwsRequestOverrideConfiguration modifiedRequestOverrideConfig =
putObjectRequest.overrideConfiguration()
.map(o -> o.toBuilder().applyMutation(builderMutation).build())
.orElseGet(() -> AwsRequestOverrideConfiguration.builder()
.applyMutation(builderMutation)
.build());
return putObjectRequest.toBuilder()
.overrideConfiguration(modifiedRequestOverrideConfig)
.build();
}
private CopyObjectRequest attachSdkAttribute(CopyObjectRequest copyObjectRequest,
Consumer builderMutation) {
AwsRequestOverrideConfiguration modifiedRequestOverrideConfig =
copyObjectRequest.overrideConfiguration()
.map(o -> o.toBuilder().applyMutation(builderMutation).build())
.orElseGet(() -> AwsRequestOverrideConfiguration.builder()
.applyMutation(builderMutation)
.build());
return copyObjectRequest.toBuilder()
.overrideConfiguration(modifiedRequestOverrideConfig)
.build();
}
private GetObjectRequest attachSdkAttribute(GetObjectRequest request,
Consumer builderMutation) {
AwsRequestOverrideConfiguration modifiedRequestOverrideConfig =
request.overrideConfiguration()
.map(o -> o.toBuilder().applyMutation(builderMutation).build())
.orElseGet(() -> AwsRequestOverrideConfiguration.builder()
.applyMutation(builderMutation)
.build());
return request.toBuilder()
.overrideConfiguration(modifiedRequestOverrideConfig)
.build();
}
@Override
public final DirectoryUpload uploadDirectory(UploadDirectoryRequest uploadDirectoryRequest) {
Validate.paramNotNull(uploadDirectoryRequest, "uploadDirectoryRequest");
try {
assertNotUnsupportedArn(uploadDirectoryRequest.bucket(), "uploadDirectory");
return uploadDirectoryHelper.uploadDirectory(uploadDirectoryRequest);
} catch (Throwable throwable) {
return new DefaultDirectoryUpload(CompletableFutureUtils.failedFuture(throwable));
}
}
@Override
public final Download download(DownloadRequest downloadRequest) {
Validate.paramNotNull(downloadRequest, "downloadRequest");
AsyncResponseTransformer responseTransformer =
downloadRequest.responseTransformer();
CompletableFuture> returnFuture = new CompletableFuture<>();
TransferProgressUpdater progressUpdater = new TransferProgressUpdater(downloadRequest, null);
progressUpdater.transferInitiated();
responseTransformer = isS3ClientMultipartEnabled()
? progressUpdater.wrapResponseTransformerForMultipartDownload(
responseTransformer, downloadRequest.getObjectRequest())
: progressUpdater.wrapResponseTransformer(responseTransformer);
progressUpdater.registerCompletion(returnFuture);
try {
assertNotUnsupportedArn(downloadRequest.getObjectRequest().bucket(), "download");
CompletableFuture future = s3AsyncClient.getObject(downloadRequest.getObjectRequest(), responseTransformer);
// Forward download cancellation to future
CompletableFutureUtils.forwardExceptionTo(returnFuture, future);
CompletableFutureUtils.forwardTransformedResultTo(future, returnFuture,
r -> CompletedDownload.builder()
.result(r)
.build());
} catch (Throwable throwable) {
returnFuture.completeExceptionally(throwable);
}
return new DefaultDownload<>(returnFuture, progressUpdater.progress());
}
@Override
public final FileDownload downloadFile(DownloadFileRequest downloadRequest) {
Validate.paramNotNull(downloadRequest, "downloadFileRequest");
GetObjectRequest getObjectRequestWithAttributes = attachSdkAttribute(
downloadRequest.getObjectRequest(),
b -> b.putExecutionAttribute(MULTIPART_DOWNLOAD_RESUME_CONTEXT, new MultipartDownloadResumeContext()));
DownloadFileRequest downloadFileRequestWithAttributes =
downloadRequest.copy(downloadFileRequest -> downloadFileRequest.getObjectRequest(getObjectRequestWithAttributes));
AsyncResponseTransformer responseTransformer =
AsyncResponseTransformer.toFile(downloadFileRequestWithAttributes.destination(),
FileTransformerConfiguration.defaultCreateOrReplaceExisting());
CompletableFuture returnFuture = new CompletableFuture<>();
TransferProgressUpdater progressUpdater = doDownloadFile(
downloadFileRequestWithAttributes, responseTransformer, returnFuture);
return new DefaultFileDownload(returnFuture, progressUpdater.progress(), () -> downloadFileRequestWithAttributes, null);
}
private TransferProgressUpdater doDownloadFile(
DownloadFileRequest downloadRequest,
AsyncResponseTransformer responseTransformer,
CompletableFuture returnFuture) {
TransferProgressUpdater progressUpdater = new TransferProgressUpdater(downloadRequest, null);
try {
progressUpdater.transferInitiated();
responseTransformer = isS3ClientMultipartEnabled()
? progressUpdater.wrapResponseTransformerForMultipartDownload(
responseTransformer, downloadRequest.getObjectRequest())
: progressUpdater.wrapResponseTransformer(responseTransformer);
progressUpdater.registerCompletion(returnFuture);
assertNotUnsupportedArn(downloadRequest.getObjectRequest().bucket(), "download");
CompletableFuture future = s3AsyncClient.getObject(
downloadRequest.getObjectRequest(), responseTransformer);
// Forward download cancellation to future
CompletableFutureUtils.forwardExceptionTo(returnFuture, future);
CompletableFutureUtils.forwardTransformedResultTo(future, returnFuture,
res -> CompletedFileDownload.builder()
.response(res)
.build());
} catch (Throwable throwable) {
returnFuture.completeExceptionally(throwable);
}
return progressUpdater;
}
@Override
public final FileDownload resumeDownloadFile(ResumableFileDownload resumableFileDownload) {
Validate.paramNotNull(resumableFileDownload, "resumableFileDownload");
// check if the multipart-download was already completed and handle it gracefully.
Optional optCtx =
multipartDownloadResumeContext(resumableFileDownload.downloadFileRequest().getObjectRequest());
if (optCtx.map(MultipartDownloadResumeContext::isComplete).orElse(false)) {
log.debug(() -> "The multipart download associated to the provided ResumableFileDownload is already completed, "
+ "nothing to resume");
return completedDownload(resumableFileDownload, optCtx.get());
}
CompletableFuture returnFuture = new CompletableFuture<>();
DownloadFileRequest originalDownloadRequest = resumableFileDownload.downloadFileRequest();
GetObjectRequest getObjectRequest = originalDownloadRequest.getObjectRequest();
CompletableFuture progressFuture = new CompletableFuture<>();
CompletableFuture newDownloadFileRequestFuture = new CompletableFuture<>();
CompletableFuture headFuture =
s3AsyncClient.headObject(b -> b.bucket(getObjectRequest.bucket()).key(getObjectRequest.key()));
// Ensure cancellations are forwarded to the head future
CompletableFutureUtils.forwardExceptionTo(returnFuture, headFuture);
headFuture.thenAccept(headObjectResponse -> {
Pair>
requestPair = toDownloadFileRequestAndTransformer(resumableFileDownload, headObjectResponse,
originalDownloadRequest);
DownloadFileRequest newDownloadFileRequest = requestPair.left();
newDownloadFileRequestFuture.complete(newDownloadFileRequest);
log.debug(() -> "Sending downloadFileRequest " + newDownloadFileRequest);
TransferProgressUpdater progressUpdater = doDownloadFile(newDownloadFileRequest,
requestPair.right(),
returnFuture);
progressFuture.complete(progressUpdater.progress());
}).exceptionally(throwable -> {
handleException(returnFuture, progressFuture, newDownloadFileRequestFuture, throwable);
return null;
});
return new DefaultFileDownload(returnFuture,
new ResumeTransferProgress(progressFuture),
() -> newOrOriginalRequestForPause(newDownloadFileRequestFuture, originalDownloadRequest),
resumableFileDownload);
}
private FileDownload completedDownload(ResumableFileDownload resumableFileDownload, MultipartDownloadResumeContext ctx) {
CompletedFileDownload completedFileDownload = CompletedFileDownload.builder().response(ctx.response()).build();
DefaultTransferProgressSnapshot completedProgressSnapshot =
DefaultTransferProgressSnapshot.builder()
.sdkResponse(ctx.response())
.totalBytes(ctx.bytesToLastCompletedParts())
.transferredBytes(resumableFileDownload.bytesTransferred())
.build();
return new DefaultFileDownload(CompletableFuture.completedFuture(completedFileDownload),
new DefaultTransferProgress(completedProgressSnapshot),
resumableFileDownload::downloadFileRequest,
resumableFileDownload);
}
private DownloadFileRequest newOrOriginalRequestForPause(CompletableFuture newDownloadFuture,
DownloadFileRequest originalDownloadRequest) {
try {
return newDownloadFuture.getNow(originalDownloadRequest);
} catch (CompletionException e) {
return originalDownloadRequest;
}
}
private static void handleException(CompletableFuture returnFuture,
CompletableFuture progressFuture,
CompletableFuture newDownloadFileRequestFuture,
Throwable throwable) {
Throwable exceptionCause = throwable instanceof CompletionException ? throwable.getCause() : throwable;
Throwable propagatedException = exceptionCause instanceof SdkException || exceptionCause instanceof Error
? exceptionCause
: SdkClientException.create("Failed to resume the request", exceptionCause);
returnFuture.completeExceptionally(propagatedException);
progressFuture.completeExceptionally(propagatedException);
newDownloadFileRequestFuture.completeExceptionally(propagatedException);
}
@Override
public final DirectoryDownload downloadDirectory(DownloadDirectoryRequest downloadDirectoryRequest) {
Validate.paramNotNull(downloadDirectoryRequest, "downloadDirectoryRequest");
try {
assertNotUnsupportedArn(downloadDirectoryRequest.bucket(), "downloadDirectoryRequest");
return downloadDirectoryHelper.downloadDirectory(downloadDirectoryRequest);
} catch (Throwable throwable) {
return new DefaultDirectoryDownload(CompletableFutureUtils.failedFuture(throwable));
}
}
@Override
public final Copy copy(CopyRequest copyRequest) {
Validate.paramNotNull(copyRequest, "copyRequest");
CompletableFuture returnFuture = new CompletableFuture<>();
// set length to 10000 as reference value, since we don't make HeadObject call yet
TransferProgressUpdater progressUpdater = new TransferProgressUpdater(copyRequest, 10000L);
// TransferListener is not supported for CRT-based client, so we'll only initiate and register completion when using
// the Java-based client with multipart enabled
if (isS3ClientMultipartEnabled()) {
Consumer attachProgressListener =
b -> b.putExecutionAttribute(JAVA_PROGRESS_LISTENER, progressUpdater.multipartClientProgressListener());
CopyObjectRequest copyObjectRequest = attachSdkAttribute(copyRequest.copyObjectRequest(), attachProgressListener);
copyRequest = copyRequest.toBuilder().copyObjectRequest(copyObjectRequest).build();
progressUpdater.transferInitiated();
progressUpdater.registerCompletion(returnFuture);
}
try {
assertNotUnsupportedArn(copyRequest.copyObjectRequest().sourceBucket(), "copy sourceBucket");
assertNotUnsupportedArn(copyRequest.copyObjectRequest().destinationBucket(), "copy destinationBucket");
CompletableFuture future =
s3AsyncClient.copyObject(copyRequest.copyObjectRequest());
// Forward transfer cancellation to future
CompletableFutureUtils.forwardExceptionTo(returnFuture, future);
CompletableFutureUtils.forwardTransformedResultTo(future, returnFuture,
r -> CompletedCopy.builder()
.response(r)
.build());
} catch (Throwable throwable) {
returnFuture.completeExceptionally(throwable);
}
return new DefaultCopy(returnFuture, progressUpdater.progress());
}
@Override
public final void close() {
if (isDefaultS3AsyncClient) {
IoUtils.closeQuietly(s3AsyncClient, log.logger());
}
IoUtils.closeQuietly(transferConfiguration, log.logger());
}
protected static void assertNotUnsupportedArn(String bucket, String operation) {
if (bucket == null) {
return;
}
if (!bucket.startsWith("arn:")) {
return;
}
if (isObjectLambdaArn(bucket)) {
String error = String.format("%s does not support S3 Object Lambda resources", operation);
throw new IllegalArgumentException(error);
}
Arn arn = Arn.fromString(bucket);
if (isMrapArn(arn)) {
String error = String.format("%s does not support S3 multi-region access point ARN", operation);
throw new IllegalArgumentException(error);
}
}
private static boolean isObjectLambdaArn(String arn) {
return arn.contains(":s3-object-lambda");
}
private static boolean isMrapArn(Arn arn) {
S3Resource s3Resource = S3ArnConverter.create().convertArn(arn);
S3AccessPointResource s3EndpointResource =
Validate.isInstanceOf(S3AccessPointResource.class, s3Resource,
"An ARN was passed as a bucket parameter to an S3 operation, however it does not "
+ "appear to be a valid S3 access point ARN.");
return !s3EndpointResource.region().isPresent();
}
}