com.amazonaws.mobileconnectors.s3.transfermanager.internal.UploadMonitor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aws-java-sdk-s3 Show documentation
Show all versions of aws-java-sdk-s3 Show documentation
The AWS Java Mobile SDK for Amazon S3 module holds the client classes that are used for communicating with Amazon Simple Storage Service
The newest version!
/*
* Copyright 2011-2018 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.
* You may obtain a copy of the License at:
*
* http://aws.amazon.com/apache2.0
*
* 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 com.amazonaws.mobileconnectors.s3.transfermanager.internal;
import com.amazonaws.AmazonClientException;
import com.amazonaws.event.ProgressEvent;
import com.amazonaws.event.ProgressListenerCallbackExecutor;
import com.amazonaws.event.ProgressListenerChain;
import com.amazonaws.mobileconnectors.s3.transfermanager.PauseResult;
import com.amazonaws.mobileconnectors.s3.transfermanager.PauseStatus;
import com.amazonaws.mobileconnectors.s3.transfermanager.PersistableUpload;
import com.amazonaws.mobileconnectors.s3.transfermanager.Transfer.TransferState;
import com.amazonaws.mobileconnectors.s3.transfermanager.TransferManager;
import com.amazonaws.mobileconnectors.s3.transfermanager.TransferManagerConfiguration;
import com.amazonaws.mobileconnectors.s3.transfermanager.model.UploadResult;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest;
import com.amazonaws.services.s3.model.CompleteMultipartUploadResult;
import com.amazonaws.services.s3.model.PartETag;
import com.amazonaws.services.s3.model.PutObjectRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Manages an upload by periodically checking to see if the upload is done, and
* returning a result if so. Otherwise, schedules a copy of itself to be run in
* the future and returns null. When waiting on the result of this class via a
* Future object, clients must call {@link UploadMonitor#isDone()} and
* {@link UploadMonitor#getFuture()}
*/
public class UploadMonitor implements Callable, TransferMonitor {
private final AmazonS3 s3;
private final ExecutorService threadPool;
private final PutObjectRequest putObjectRequest;
private ScheduledExecutorService timedThreadPool;
private static final Log log = LogFactory.getLog(UploadMonitor.class);
private final TransferManagerConfiguration configuration;
private final ProgressListenerCallbackExecutor progressListenerChainCallbackExecutor;
private final UploadCallable multipartUploadCallable;
private final UploadImpl transfer;
/*
* State for tracking the upload's progress
*/
private String uploadId;
private final List> futures = new ArrayList>();
/*
* State for clients wishing to poll for completion
*/
private boolean isUploadDone = false;
private Future nextFuture;
@Override
public synchronized Future getFuture() {
return nextFuture;
}
private synchronized void setNextFuture(Future nextFuture) {
this.nextFuture = nextFuture;
}
@Override
public synchronized boolean isDone() {
return isUploadDone;
}
private synchronized void markAllDone() {
isUploadDone = true;
}
// TODO: this could be configured in the configuration object (which we're
// not using right now)
private int pollInterval = 5000;
/**
* Constructs a new upload watcher, which immediately submits itself to the
* thread pool.
*
* @param manager The {@link TransferManager} that owns this upload.
* @param transfer The transfer being processed.
* @param threadPool The {@link ExecutorService} to which we should submit
* new tasks.
* @param multipartUploadCallable The callable responsible for processing
* the upload asynchronously
* @param putObjectRequest The original putObject request
* @param progressListenerChain A chain of listeners that wish to be
* notified of upload progress
*/
public UploadMonitor(TransferManager manager, UploadImpl transfer, ExecutorService threadPool,
UploadCallable multipartUploadCallable, PutObjectRequest putObjectRequest,
ProgressListenerChain progressListenerChain) {
this.s3 = manager.getAmazonS3Client();
this.configuration = manager.getConfiguration();
this.multipartUploadCallable = multipartUploadCallable;
this.threadPool = threadPool;
this.putObjectRequest = putObjectRequest;
this.progressListenerChainCallbackExecutor = ProgressListenerCallbackExecutor
.wrapListener(progressListenerChain);
this.transfer = transfer;
setNextFuture(threadPool.submit(this));
}
public void setTimedThreadPool(ScheduledExecutorService timedThreadPool) {
this.timedThreadPool = timedThreadPool;
}
@Override
public UploadResult call() throws Exception {
try {
if (uploadId == null) {
return upload();
} else {
return poll();
}
} catch (CancellationException e) {
transfer.setState(TransferState.Canceled);
fireProgressEvent(ProgressEvent.CANCELED_EVENT_CODE);
throw new AmazonClientException("Upload canceled");
} catch (Exception e) {
transfer.setState(TransferState.Failed);
fireProgressEvent(ProgressEvent.FAILED_EVENT_CODE);
throw e;
}
}
/**
* Polls for a result from a multipart upload and either returns it if
* complete, or reschedules to poll again later if not.
*/
private UploadResult poll() throws InterruptedException {
for (Future f : futures) {
if (!f.isDone()) {
reschedule();
return null;
}
}
for (Future f : futures) {
if (f.isCancelled()) {
throw new CancellationException();
}
}
return completeMultipartUpload();
}
/**
* Initiates the upload and checks on the result. If it has completed,
* returns the result; otherwise, reschedules to check back later.
*/
private UploadResult upload() throws Exception, InterruptedException {
UploadResult result = multipartUploadCallable.call();
if (result != null) {
uploadComplete();
} else {
uploadId = multipartUploadCallable.getMultipartUploadId();
futures.addAll(multipartUploadCallable.getFutures());
reschedule();
}
return result;
}
private void uploadComplete() {
markAllDone();
transfer.setState(TransferState.Completed);
// AmazonS3Client takes care of all the events for single part uploads,
// so we only need to send a completed event for multipart uploads.
if (multipartUploadCallable.isMultipartUpload()) {
fireProgressEvent(ProgressEvent.COMPLETED_EVENT_CODE);
}
}
private void reschedule() {
setNextFuture(timedThreadPool.schedule(new Callable() {
@Override
public UploadResult call() throws Exception {
setNextFuture(threadPool.submit(UploadMonitor.this));
return null;
}
}, pollInterval, TimeUnit.MILLISECONDS));
}
private void fireProgressEvent(final int eventType) {
if (progressListenerChainCallbackExecutor == null)
return;
ProgressEvent event = new ProgressEvent(0);
event.setEventCode(eventType);
progressListenerChainCallbackExecutor.progressChanged(event);
}
/**
* Completes the multipart upload and returns the result.
*/
private UploadResult completeMultipartUpload() {
CompleteMultipartUploadResult completeMultipartUploadResult = s3
.completeMultipartUpload(new CompleteMultipartUploadRequest(putObjectRequest
.getBucketName(),
putObjectRequest.getKey(), uploadId, collectPartETags()));
uploadComplete();
UploadResult uploadResult = new UploadResult();
uploadResult.setBucketName(completeMultipartUploadResult.getBucketName());
uploadResult.setKey(completeMultipartUploadResult.getKey());
uploadResult.setETag(completeMultipartUploadResult.getETag());
uploadResult.setVersionId(completeMultipartUploadResult.getVersionId());
return uploadResult;
}
private List collectPartETags() {
final List partETags = new ArrayList();
partETags.addAll(multipartUploadCallable.getETags());
for (Future future : futures) {
try {
partETags.add(future.get());
} catch (Exception e) {
throw new AmazonClientException("Unable to upload part: "
+ e.getCause().getMessage(), e.getCause());
}
}
return partETags;
}
/**
* Cancels the futures in the following cases - If the user has requested
* for forcefully aborting the transfers. - If the upload is a multi part
* parellel upload. - If the upload operation hasn't started. Cancels all
* the in flight transfers of the upload if applicable. Returns the
* multi-part upload Id in case of the parallel multi-part uploads. Returns
* null otherwise.
*/
PauseResult pause(boolean forceCancel) {
PersistableUpload persistableUpload = multipartUploadCallable
.getPersistableUpload();
if (persistableUpload == null) {
PauseStatus pauseStatus = TransferManagerUtils
.determinePauseStatus(transfer.getState(), forceCancel);
if (forceCancel) {
cancelFutures();
multipartUploadCallable.performAbortMultipartUpload();
}
return new PauseResult(pauseStatus);
}
cancelFutures();
return new PauseResult(PauseStatus.SUCCESS,
persistableUpload);
}
/**
* Cancels the inflight transfers if they are not completed.
*/
private void cancelFutures() {
nextFuture.cancel(true);
for (Future f : futures) {
f.cancel(true);
}
multipartUploadCallable.getFutures().clear();
futures.clear();
}
/**
* Cancels all the futures associated with this upload operation. Also
* cleans up the parts on Amazon S3 if the uplaod is performed as a
* multi-part upload operation.
*/
void performAbort() {
cancelFutures();
multipartUploadCallable.performAbortMultipartUpload();
}
}