Skip to content

Commit 91676df

Browse files
author
Bennett Lynch
committed
Add support for S3TransferManager TransferListeners
This adds initial support for S3TransferManager TransferListeners. The motivation and design is consistent as outlined in aws#2729. It also addresses some customer asks as mentioned in aws#37. Every @SdkPublicApi has been thoroughly documented with its description and usage instructions where applicable.
1 parent bb5d794 commit 91676df

24 files changed

+1829
-12
lines changed

core/annotations/src/main/java/software/amazon/awssdk/annotations/Immutable.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
* Based on code developed by Brian Goetz and Tim Peierls and concepts
4141
* published in 'Java Concurrency in Practice' by Brian Goetz, Tim Peierls,
4242
* Joshua Bloch, Joseph Bowbeer, David Holmes and Doug Lea.
43+
*
44+
* @see Mutable
4345
*/
4446
@Documented
4547
@Target(ElementType.TYPE)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.annotations;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
/**
25+
* The class to which this annotation is applied is explicitly mutable,
26+
* meaning that its state is subject to change between calls. Mutable
27+
* classes offer no inherent guarantees on thread-safety. Where possible,
28+
* classes may be further annotated as either {@link ThreadSafe} or
29+
* {@link NotThreadSafe}.
30+
*
31+
* @see Immutable
32+
*/
33+
@Documented
34+
@Target(ElementType.TYPE)
35+
@Retention(RetentionPolicy.CLASS)
36+
@SdkProtectedApi
37+
public @interface Mutable {
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.transfer.s3;
17+
18+
import java.math.BigDecimal;
19+
import java.math.RoundingMode;
20+
import java.util.Collections;
21+
22+
/**
23+
* An example implementation of {@link TransferListener} that prints a progress bar to System.out. This example is referenced to
24+
* in the {@link TransferListener} documentation and also used in some {@link S3TransferManager} integration tests
25+
*/
26+
public class ProgressPrintingTransferListener implements TransferListener {
27+
28+
private final ProgressBar progressBar = new ProgressBar(20);
29+
30+
@Override
31+
public void transferInitiated(Context.TransferInitiated context) {
32+
System.out.println("Transfer initiated...");
33+
context.progressSnapshot().ratioTransferred().ifPresent(progressBar::update);
34+
}
35+
36+
@Override
37+
public void bytesTransferred(Context.BytesTransferred context) {
38+
context.progressSnapshot().ratioTransferred().ifPresent(progressBar::update);
39+
}
40+
41+
@Override
42+
public void transferComplete(Context.TransferComplete context) {
43+
context.progressSnapshot().ratioTransferred().ifPresent(progressBar::update);
44+
System.out.println("Transfer complete!");
45+
}
46+
47+
@Override
48+
public void transferFailed(Context.TransferFailed context) {
49+
System.out.println("Transfer failed.");
50+
context.exception().printStackTrace();
51+
}
52+
53+
private static class ProgressBar {
54+
private final int maxTicks;
55+
private int prevTicks = -1;
56+
57+
public ProgressBar(int maxTicks) {
58+
this.maxTicks = maxTicks;
59+
}
60+
61+
public void update(double ratio) {
62+
int ticks = (int) Math.floor(ratio * maxTicks);
63+
if (ticks != prevTicks) {
64+
System.out.printf("|%s%s| %s%n",
65+
repeat("=", ticks),
66+
repeat(" ", maxTicks - ticks),
67+
round(ratio * 100, 1) + "%");
68+
prevTicks = ticks;
69+
}
70+
}
71+
72+
private static String repeat(String str, int n) {
73+
return String.join("", Collections.nCopies(n, str));
74+
}
75+
76+
private static double round(double value, int places) {
77+
BigDecimal bd = BigDecimal.valueOf(value);
78+
bd = bd.setScale(places, RoundingMode.HALF_DOWN);
79+
return bd.doubleValue();
80+
}
81+
}
82+
}

services-custom/s3-transfer-manager/src/it/java/software/amazon/awssdk/transfer/s3/S3TransferManagerDownloadIntegrationTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@
3131
public class S3TransferManagerDownloadIntegrationTest extends S3IntegrationTestBase {
3232
private static final String BUCKET = temporaryBucketName(S3TransferManagerDownloadIntegrationTest.class);
3333
private static final String KEY = "key";
34+
private static final int OBJ_SIZE = 16 * 1024 * 1024;
3435
private static S3TransferManager transferManager;
3536
private static File file;
3637

3738
@BeforeClass
3839
public static void setup() throws IOException {
3940
createBucket(BUCKET);
40-
file = new RandomTempFile(10_000);
41+
file = new RandomTempFile(OBJ_SIZE);
4142
s3.putObject(PutObjectRequest.builder()
4243
.bucket(BUCKET)
4344
.key(KEY)
@@ -59,7 +60,8 @@ public static void cleanup() {
5960
public void download_shouldWork() throws IOException {
6061
Path path = RandomTempFile.randomUncreatedFile().toPath();
6162
Download download = transferManager.download(b -> b.getObjectRequest(r -> r.bucket(BUCKET).key(KEY))
62-
.destination(path));
63+
.destination(path)
64+
.listeners(new ProgressPrintingTransferListener()));
6365
CompletedDownload completedDownload = download.completionFuture().join();
6466
assertThat(Md5Utils.md5AsBase64(path.toFile())).isEqualTo(Md5Utils.md5AsBase64(file));
6567
assertThat(completedDownload.response().responseMetadata().requestId()).isNotNull();

services-custom/s3-transfer-manager/src/it/java/software/amazon/awssdk/transfer/s3/S3TransferManagerUploadIntegrationTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030

3131
public class S3TransferManagerUploadIntegrationTest extends S3IntegrationTestBase {
3232
private static final String TEST_BUCKET = temporaryBucketName(S3TransferManagerUploadIntegrationTest.class);
33-
private static final String TEST_KEY = "8mib_file.dat";
34-
private static final int OBJ_SIZE = 8 * 1024 * 1024;
33+
private static final String TEST_KEY = "16mib_file.dat";
34+
private static final int OBJ_SIZE = 16 * 1024 * 1024;
3535

3636
private static RandomTempFile testFile;
3737
private static S3TransferManager tm;
@@ -64,6 +64,7 @@ public void upload_fileSentCorrectly() throws IOException {
6464
Upload upload = tm.upload(UploadRequest.builder()
6565
.putObjectRequest(b -> b.bucket(TEST_BUCKET).key(TEST_KEY))
6666
.source(testFile.toPath())
67+
.listeners(new ProgressPrintingTransferListener())
6768
.build());
6869

6970
CompletedUpload completedUpload = upload.completionFuture().join();
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.transfer.s3;
17+
18+
import java.util.concurrent.CompletionException;
19+
import software.amazon.awssdk.annotations.SdkPreviewApi;
20+
import software.amazon.awssdk.annotations.SdkProtectedApi;
21+
import software.amazon.awssdk.annotations.SdkPublicApi;
22+
import software.amazon.awssdk.annotations.ThreadSafe;
23+
24+
/**
25+
* A wrapper class that groups together the different context objects that are exposed to {@link TransferListener}s.
26+
*
27+
* @see TransferListener
28+
*/
29+
@SdkProtectedApi
30+
public final class Context {
31+
private Context() {
32+
}
33+
34+
/**
35+
* A new transfer has been initiated.
36+
* <p>
37+
* Available context attributes:
38+
* <ol>
39+
* <li>{@link Context.TransferInitiated#request()}</li>
40+
* <li>{@link Context.TransferInitiated#progressSnapshot()}</li>
41+
* </ol>
42+
*/
43+
@ThreadSafe
44+
@SdkPublicApi
45+
@SdkPreviewApi
46+
public interface TransferInitiated {
47+
/**
48+
* The {@link TransferRequest} that was submitted to {@link S3TransferManager}, i.e., the {@link UploadRequest} or {@link
49+
* DownloadRequest}.
50+
*/
51+
TransferRequest request();
52+
53+
/**
54+
* The immutable {@link TransferProgressSnapshot} for this specific update.
55+
*/
56+
TransferProgressSnapshot progressSnapshot();
57+
}
58+
59+
/**
60+
* Additional bytes have been submitted or received.
61+
* <p>
62+
* Available context attributes:
63+
* <ol>
64+
* <li>{@link Context.BytesTransferred#request()}</li>
65+
* <li>{@link Context.BytesTransferred#progressSnapshot()}</li>
66+
* </ol>
67+
*/
68+
@ThreadSafe
69+
@SdkPublicApi
70+
@SdkPreviewApi
71+
public interface BytesTransferred extends TransferInitiated {
72+
}
73+
74+
/**
75+
* The transfer has completed successfully.
76+
* <p>
77+
* Available context attributes:
78+
* <ol>
79+
* <li>{@link Context.TransferComplete#request()}</li>
80+
* <li>{@link Context.TransferComplete#progressSnapshot()}</li>
81+
* <li>{@link Context.TransferComplete#completedTransfer()}</li>
82+
* </ol>
83+
*/
84+
@ThreadSafe
85+
@SdkPublicApi
86+
@SdkPreviewApi
87+
public interface TransferComplete extends BytesTransferred {
88+
/**
89+
* The completed transfer, i.e., the {@link CompletedUpload} or {@link CompletedDownload}.
90+
*/
91+
CompletedTransfer completedTransfer();
92+
}
93+
94+
/**
95+
* The transfer failed.
96+
* <p>
97+
* Available context attributes:
98+
* <ol>
99+
* <li>{@link Context.TransferFailed#request()}</li>
100+
* <li>{@link Context.TransferFailed#progressSnapshot()}</li>
101+
* <li>{@link Context.TransferFailed#exception()}</li>
102+
* </ol>
103+
*/
104+
@ThreadSafe
105+
@SdkPublicApi
106+
@SdkPreviewApi
107+
public interface TransferFailed extends TransferInitiated {
108+
/**
109+
* The exception associated with the failed transfer.
110+
* <p>
111+
* Note that this would be the <i>cause</i>> of a {@link CompletionException}, and not a {@link CompletionException}
112+
* itself.
113+
*/
114+
Throwable exception();
115+
}
116+
}

0 commit comments

Comments
 (0)