19
19
import java .util .ArrayList ;
20
20
import java .util .List ;
21
21
import java .util .concurrent .CompletableFuture ;
22
- import java .util .concurrent .CompletionException ;
23
22
import java .util .concurrent .atomic .AtomicReferenceArray ;
24
- import java .util .function .BiFunction ;
25
- import java .util .function .Supplier ;
26
23
import java .util .stream .IntStream ;
27
24
import software .amazon .awssdk .annotations .SdkInternalApi ;
28
- import software .amazon .awssdk .core .exception .SdkClientException ;
29
25
import software .amazon .awssdk .services .s3 .S3AsyncClient ;
30
- import software .amazon .awssdk .services .s3 .model . AbortMultipartUploadRequest ;
26
+ import software .amazon .awssdk .services .s3 .internal . multipart . GenericMultipartHelper ;
31
27
import software .amazon .awssdk .services .s3 .model .CompleteMultipartUploadRequest ;
32
28
import software .amazon .awssdk .services .s3 .model .CompleteMultipartUploadResponse ;
33
29
import software .amazon .awssdk .services .s3 .model .CompletedMultipartUpload ;
50
46
public final class CopyObjectHelper {
51
47
private static final Logger log = Logger .loggerFor (S3AsyncClient .class );
52
48
53
- /**
54
- * The max number of parts on S3 side is 10,000
55
- */
56
- private static final long MAX_UPLOAD_PARTS = 10_000 ;
57
-
58
49
private final S3AsyncClient s3AsyncClient ;
59
50
private final long partSizeInBytes ;
51
+ private final GenericMultipartHelper <CopyObjectRequest , CopyObjectResponse > genericMultipartHelper ;
60
52
61
53
public CopyObjectHelper (S3AsyncClient s3AsyncClient , long partSizeInBytes ) {
62
54
this .s3AsyncClient = s3AsyncClient ;
63
55
this .partSizeInBytes = partSizeInBytes ;
56
+ this .genericMultipartHelper = new GenericMultipartHelper <>(s3AsyncClient ,
57
+ RequestConversionUtils ::toAbortMultipartUploadRequest ,
58
+ RequestConversionUtils ::toCopyObjectResponse );
64
59
}
65
60
66
61
public CompletableFuture <CopyObjectResponse > copyObject (CopyObjectRequest copyObjectRequest ) {
@@ -69,14 +64,15 @@ public CompletableFuture<CopyObjectResponse> copyObject(CopyObjectRequest copyOb
69
64
70
65
try {
71
66
CompletableFuture <HeadObjectResponse > headFuture =
72
- s3AsyncClient .headObject (CopyRequestConversionUtils .toHeadObjectRequest (copyObjectRequest ));
67
+ s3AsyncClient .headObject (RequestConversionUtils .toHeadObjectRequest (copyObjectRequest ));
73
68
74
69
// Ensure cancellations are forwarded to the head future
75
70
CompletableFutureUtils .forwardExceptionTo (returnFuture , headFuture );
76
71
77
72
headFuture .whenComplete ((headObjectResponse , throwable ) -> {
78
73
if (throwable != null ) {
79
- handleException (returnFuture , () -> "Failed to retrieve metadata from the source object" , throwable );
74
+ genericMultipartHelper .handleException (returnFuture , () -> "Failed to retrieve metadata from the source "
75
+ + "object" , throwable );
80
76
} else {
81
77
doCopyObject (copyObjectRequest , returnFuture , headObjectResponse );
82
78
}
@@ -105,7 +101,7 @@ private void copyInParts(CopyObjectRequest copyObjectRequest,
105
101
Long contentLength ,
106
102
CompletableFuture <CopyObjectResponse > returnFuture ) {
107
103
108
- CreateMultipartUploadRequest request = CopyRequestConversionUtils .toCreateMultipartUploadRequest (copyObjectRequest );
104
+ CreateMultipartUploadRequest request = RequestConversionUtils .toCreateMultipartUploadRequest (copyObjectRequest );
109
105
CompletableFuture <CreateMultipartUploadResponse > createMultipartUploadFuture =
110
106
s3AsyncClient .createMultipartUpload (request );
111
107
@@ -114,25 +110,22 @@ private void copyInParts(CopyObjectRequest copyObjectRequest,
114
110
115
111
createMultipartUploadFuture .whenComplete ((createMultipartUploadResponse , throwable ) -> {
116
112
if (throwable != null ) {
117
- handleException (returnFuture , () -> "Failed to initiate multipart upload" , throwable );
113
+ genericMultipartHelper . handleException (returnFuture , () -> "Failed to initiate multipart upload" , throwable );
118
114
} else {
119
115
log .debug (() -> "Initiated new multipart upload, uploadId: " + createMultipartUploadResponse .uploadId ());
120
116
doCopyInParts (copyObjectRequest , contentLength , returnFuture , createMultipartUploadResponse .uploadId ());
121
117
}
122
118
});
123
119
}
124
120
125
- private int determinePartCount (long contentLength , long partSize ) {
126
- return (int ) Math .ceil (contentLength / (double ) partSize );
127
- }
128
-
129
121
private void doCopyInParts (CopyObjectRequest copyObjectRequest ,
130
122
Long contentLength ,
131
123
CompletableFuture <CopyObjectResponse > returnFuture ,
132
124
String uploadId ) {
133
- long optimalPartSize = calculateOptimalPartSizeForCopy (contentLength );
134
125
135
- int partCount = determinePartCount (contentLength , optimalPartSize );
126
+ long optimalPartSize = genericMultipartHelper .calculateOptimalPartSizeFor (contentLength , partSizeInBytes );
127
+
128
+ int partCount = genericMultipartHelper .determinePartCount (contentLength , optimalPartSize );
136
129
137
130
log .debug (() -> String .format ("Starting multipart copy with partCount: %s, optimalPartSize: %s" ,
138
131
partCount , optimalPartSize ));
@@ -147,32 +140,15 @@ private void doCopyInParts(CopyObjectRequest copyObjectRequest,
147
140
optimalPartSize );
148
141
CompletableFutureUtils .allOfExceptionForwarded (futures .toArray (new CompletableFuture [0 ]))
149
142
.thenCompose (ignore -> completeMultipartUpload (copyObjectRequest , uploadId , completedParts ))
150
- .handle (handleExceptionOrResponse (copyObjectRequest , returnFuture , uploadId ))
143
+ .handle (genericMultipartHelper .handleExceptionOrResponse (copyObjectRequest , returnFuture ,
144
+ uploadId ))
151
145
.exceptionally (throwable -> {
152
- handleException (returnFuture , () -> "Unexpected exception occurred" , throwable );
146
+ genericMultipartHelper .handleException (returnFuture , () -> "Unexpected exception occurred" ,
147
+ throwable );
153
148
return null ;
154
149
});
155
150
}
156
151
157
- private BiFunction <CompleteMultipartUploadResponse , Throwable , Void > handleExceptionOrResponse (
158
- CopyObjectRequest copyObjectRequest ,
159
- CompletableFuture <CopyObjectResponse > returnFuture ,
160
- String uploadId ) {
161
-
162
- return (completeMultipartUploadResponse , throwable ) -> {
163
- if (throwable != null ) {
164
- cleanUpParts (copyObjectRequest , uploadId );
165
- handleException (returnFuture , () -> "Failed to send multipart copy requests." ,
166
- throwable );
167
- } else {
168
- returnFuture .complete (CopyRequestConversionUtils .toCopyObjectResponse (
169
- completeMultipartUploadResponse ));
170
- }
171
-
172
- return null ;
173
- };
174
- }
175
-
176
152
private CompletableFuture <CompleteMultipartUploadResponse > completeMultipartUpload (
177
153
CopyObjectRequest copyObjectRequest , String uploadId , AtomicReferenceArray <CompletedPart > completedParts ) {
178
154
log .debug (() -> String .format ("Sending completeMultipartUploadRequest, uploadId: %s" ,
@@ -194,35 +170,6 @@ private CompletableFuture<CompleteMultipartUploadResponse> completeMultipartUplo
194
170
return s3AsyncClient .completeMultipartUpload (completeMultipartUploadRequest );
195
171
}
196
172
197
- private void cleanUpParts (CopyObjectRequest copyObjectRequest , String uploadId ) {
198
- AbortMultipartUploadRequest abortMultipartUploadRequest =
199
- CopyRequestConversionUtils .toAbortMultipartUploadRequest (copyObjectRequest , uploadId );
200
- s3AsyncClient .abortMultipartUpload (abortMultipartUploadRequest )
201
- .exceptionally (throwable -> {
202
- log .warn (() -> String .format ("Failed to abort previous multipart upload "
203
- + "(id: %s)"
204
- + ". You may need to call "
205
- + "S3AsyncClient#abortMultiPartUpload to "
206
- + "free all storage consumed by"
207
- + " all parts. " ,
208
- uploadId ), throwable );
209
- return null ;
210
- });
211
- }
212
-
213
- private static void handleException (CompletableFuture <CopyObjectResponse > returnFuture ,
214
- Supplier <String > message ,
215
- Throwable throwable ) {
216
- Throwable cause = throwable instanceof CompletionException ? throwable .getCause () : throwable ;
217
-
218
- if (cause instanceof Error ) {
219
- returnFuture .completeExceptionally (cause );
220
- } else {
221
- SdkClientException exception = SdkClientException .create (message .get (), cause );
222
- returnFuture .completeExceptionally (exception );
223
- }
224
- }
225
-
226
173
private List <CompletableFuture <CompletedPart >> sendUploadPartCopyRequests (CopyObjectRequest copyObjectRequest ,
227
174
long contentLength ,
228
175
String uploadId ,
@@ -265,23 +212,13 @@ private static CompletedPart convertUploadPartCopyResponse(AtomicReferenceArray<
265
212
UploadPartCopyResponse uploadPartCopyResponse ) {
266
213
CopyPartResult copyPartResult = uploadPartCopyResponse .copyPartResult ();
267
214
CompletedPart completedPart =
268
- CopyRequestConversionUtils .toCompletedPart (copyPartResult ,
269
- partNumber );
215
+ RequestConversionUtils .toCompletedPart (copyPartResult ,
216
+ partNumber );
270
217
271
218
completedParts .set (partNumber - 1 , completedPart );
272
219
return completedPart ;
273
220
}
274
221
275
- /**
276
- * Calculates the optimal part size of each part request if the copy operation is carried out as multipart copy.
277
- */
278
- private long calculateOptimalPartSizeForCopy (long contentLengthOfSource ) {
279
- double optimalPartSize = contentLengthOfSource / (double ) MAX_UPLOAD_PARTS ;
280
-
281
- optimalPartSize = Math .ceil (optimalPartSize );
282
- return (long ) Math .max (optimalPartSize , partSizeInBytes );
283
- }
284
-
285
222
private void copyInOneChunk (CopyObjectRequest copyObjectRequest ,
286
223
CompletableFuture <CopyObjectResponse > returnFuture ) {
287
224
CompletableFuture <CopyObjectResponse > copyObjectFuture =
0 commit comments