/* * Copyright 2010 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 com.amazonaws.services.s3.transfer.internal; 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 org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.amazonaws.AmazonClientException; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.AbortMultipartUploadRequest; import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest; import com.amazonaws.services.s3.model.CompleteMultipartUploadResult; import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest; import com.amazonaws.services.s3.model.PartETag; import com.amazonaws.services.s3.model.ProgressEvent; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.StorageClass; import com.amazonaws.services.s3.model.UploadPartRequest; import com.amazonaws.services.s3.transfer.TransferManager; import com.amazonaws.services.s3.transfer.TransferManagerConfiguration; import com.amazonaws.services.s3.transfer.model.UploadResult; public class MultipartUploadCallable implements Callable<UploadResult> { private final AmazonS3 s3; private final ExecutorService threadPool; private final PutObjectRequest putObjectRequest; private static final Log log = LogFactory.getLog(MultipartUploadCallable.class); private final TransferManagerConfiguration configuration; private final ProgressListenerChain progressListenerChain; public MultipartUploadCallable(TransferManager transferManager, ExecutorService threadPool, PutObjectRequest putObjectRequest, ProgressListenerChain progressListenerChain) { this.s3 = transferManager.getAmazonS3Client(); this.configuration = transferManager.getConfiguration(); this.threadPool = threadPool; this.putObjectRequest = putObjectRequest; this.progressListenerChain = progressListenerChain; } public UploadResult call() throws Exception { final String bucketName = putObjectRequest.getBucketName(); final String key = putObjectRequest.getKey(); fireProgressEvent(ProgressEvent.STARTED_EVENT_CODE); String uploadId = initiateMultipartUpload(putObjectRequest); long optimalPartSize = TransferManagerUtils.calculateOptimalPartSize(putObjectRequest, configuration); log.debug("Calculated optimal part size: " + optimalPartSize); try { final List<PartETag> partETags = new ArrayList<PartETag>(); UploadPartRequestFactory requestFactory = new UploadPartRequestFactory(putObjectRequest, uploadId, optimalPartSize); if (TransferManagerUtils.isUploadParallelizable(putObjectRequest)) { List<Future<PartETag>> futures = new ArrayList<Future<PartETag>>(); while (requestFactory.hasMoreRequests()) { if (threadPool.isShutdown()) throw new CancellationException("TransferManager has been shutdown"); UploadPartRequest request = requestFactory.getNextUploadPartRequest(); futures.add(threadPool.submit(new UploadPartCallable(s3, request))); } this.collectPartETags(futures, partETags); } else { while (requestFactory.hasMoreRequests()) { if (threadPool.isShutdown()) throw new CancellationException("TransferManager has been shutdown"); partETags.add(s3.uploadPart(requestFactory.getNextUploadPartRequest()).getPartETag()); } } CompleteMultipartUploadResult completeMultipartUploadResult = s3.completeMultipartUpload( new CompleteMultipartUploadRequest(bucketName, key, uploadId, partETags)); fireProgressEvent(ProgressEvent.COMPLETED_EVENT_CODE); UploadResult uploadResult = new UploadResult(); uploadResult.setBucketName(completeMultipartUploadResult.getBucketName()); uploadResult.setKey(completeMultipartUploadResult.getKey()); uploadResult.setETag(completeMultipartUploadResult.getETag()); uploadResult.setVersionId(completeMultipartUploadResult.getVersionId()); return uploadResult; } catch (Exception e) { fireProgressEvent(ProgressEvent.FAILED_EVENT_CODE); try { s3.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, key, uploadId)); } catch (Exception e2) { log.info("Unable to abort multipart upload, you may need to manually remove uploaded parts: " + e2.getMessage(), e2); } throw e; } finally { if (putObjectRequest.getInputStream() != null) { try {putObjectRequest.getInputStream().close(); } catch (Exception e) { log.warn("Unable to cleanly close input stream: " + e.getMessage(), e); } } } } private String initiateMultipartUpload(PutObjectRequest putObjectRequest) { InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(putObjectRequest.getBucketName(), putObjectRequest.getKey()) .withCannedACL(putObjectRequest.getCannedAcl()) .withObjectMetadata(putObjectRequest.getMetadata()); if (putObjectRequest.getStorageClass() != null) { initiateMultipartUploadRequest.setStorageClass( StorageClass.fromValue(putObjectRequest.getStorageClass())); } String uploadId = s3.initiateMultipartUpload(initiateMultipartUploadRequest).getUploadId(); log.debug("Initiated new multipart upload: " + uploadId); return uploadId; } private void fireProgressEvent(int eventType) { if (progressListenerChain == null) return; ProgressEvent event = new ProgressEvent(0); event.setEventCode(eventType); progressListenerChain.progressChanged(event); } private void collectPartETags(final List<Future<PartETag>> futures, final List<PartETag> partETags) { for (Future<PartETag> future : futures) { try { partETags.add(future.get()); } catch (Exception e) { throw new AmazonClientException("Unable to upload part: " + e.getCause().getMessage(), e.getCause()); } } } }