Skip to content

Commit d53e900

Browse files
committed
Added UploadDirectoryRequest.Builder#uploadRequestTransformer that allows modifying the UploadRequests generated during directory uploads.
1 parent 2a755b5 commit d53e900

File tree

5 files changed

+152
-5
lines changed

5 files changed

+152
-5
lines changed

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package software.amazon.awssdk.transfer.s3;
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1920
import static software.amazon.awssdk.testutils.service.S3BucketUtils.temporaryBucketName;
2021
import static software.amazon.awssdk.utils.IoUtils.closeQuietly;
2122

@@ -25,6 +26,8 @@
2526
import java.nio.file.Path;
2627
import java.nio.file.Paths;
2728
import java.util.List;
29+
import java.util.concurrent.ExecutionException;
30+
import java.util.concurrent.TimeUnit;
2831
import java.util.stream.Collectors;
2932
import org.apache.commons.lang3.RandomStringUtils;
3033
import org.junit.AfterClass;
@@ -124,6 +127,21 @@ public void uploadDirectory_withDelimiter_filesSentCorrectly() {
124127
});
125128
}
126129

130+
@Test
131+
public void uploadDirectory_withRequestFactory_usesRequestFactory() {
132+
// Generate a bad upload request on purpose and watch the failure to make sure that our request is used.
133+
assertThatThrownBy(() -> tm.uploadDirectory(r -> r.sourceDirectory(directory)
134+
.bucket(TEST_BUCKET)
135+
.uploadRequestTransformer(u -> u.toBuilder()
136+
.source((Path) null)
137+
.build()))
138+
.completionFuture()
139+
.get(10, TimeUnit.SECONDS))
140+
.isInstanceOf(ExecutionException.class)
141+
.hasCauseInstanceOf(NullPointerException.class)
142+
.hasMessageContaining("source");
143+
}
144+
127145
private static Path createLocalTestDirectory() throws IOException {
128146
Path directory = Files.createTempDirectory("test");
129147

services-custom/s3-transfer-manager/src/main/java/software/amazon/awssdk/transfer/s3/UploadDirectoryRequest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.function.Consumer;
2323
import software.amazon.awssdk.annotations.SdkPreviewApi;
2424
import software.amazon.awssdk.annotations.SdkPublicApi;
25+
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
2526
import software.amazon.awssdk.utils.Validate;
2627
import software.amazon.awssdk.utils.builder.CopyableBuilder;
2728
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
@@ -41,13 +42,15 @@ public final class UploadDirectoryRequest implements TransferRequest, ToCopyable
4142
private final String prefix;
4243
private final UploadDirectoryOverrideConfiguration overrideConfiguration;
4344
private final String delimiter;
45+
private final UploadRequestTransformer uploadRequestTransformer;
4446

4547
public UploadDirectoryRequest(DefaultBuilder builder) {
4648
this.sourceDirectory = Validate.paramNotNull(builder.sourceDirectory, "sourceDirectory");
4749
this.bucket = Validate.paramNotNull(builder.bucket, "bucket");
4850
this.prefix = builder.prefix;
4951
this.overrideConfiguration = builder.configuration;
5052
this.delimiter = builder.delimiter;
53+
this.uploadRequestTransformer = builder.uploadRequestTransformer;
5154
}
5255

5356
/**
@@ -86,6 +89,14 @@ public Optional<String> delimiter() {
8689
return Optional.ofNullable(delimiter);
8790
}
8891

92+
/**
93+
* @return the optional upload request transformer
94+
* @see Builder#uploadRequestTransformer(UploadRequestTransformer)
95+
*/
96+
public Optional<UploadRequestTransformer> uploadRequestTransformer() {
97+
return Optional.ofNullable(uploadRequestTransformer);
98+
}
99+
89100
/**
90101
* @return the optional override configuration
91102
* @see Builder#overrideConfiguration(UploadDirectoryOverrideConfiguration)
@@ -130,6 +141,9 @@ public boolean equals(Object o) {
130141
if (!Objects.equals(delimiter, that.delimiter)) {
131142
return false;
132143
}
144+
if (!Objects.equals(uploadRequestTransformer, that.uploadRequestTransformer)) {
145+
return false;
146+
}
133147
return Objects.equals(overrideConfiguration, that.overrideConfiguration);
134148
}
135149

@@ -140,6 +154,7 @@ public int hashCode() {
140154
result = 31 * result + (prefix != null ? prefix.hashCode() : 0);
141155
result = 31 * result + (delimiter != null ? delimiter.hashCode() : 0);
142156
result = 31 * result + (overrideConfiguration != null ? overrideConfiguration.hashCode() : 0);
157+
result = 31 * result + (uploadRequestTransformer != null ? uploadRequestTransformer.hashCode() : 0);
143158
return result;
144159
}
145160

@@ -220,6 +235,51 @@ public interface Builder extends CopyableBuilder<Builder, UploadDirectoryRequest
220235
*/
221236
Builder delimiter(String delimiter);
222237

238+
/**
239+
* Specify a function used to transform the {@link UploadRequest}s generated by this {@link UploadDirectoryRequest}. The
240+
* provided function is called once for each file that is uploaded, allowing you to modify the paths resolved by
241+
* TransferManager on a per-file basis, modify the created {@link PutObjectRequest} before it is passed to S3, or
242+
* configure a {@link TransferRequestOverrideConfiguration}.
243+
*
244+
* <p>The factory receives the {@link UploadRequest}s created by Transfer Manager for each file in the directory being
245+
* uploaded, and returns a (potentially modified) {@link UploadRequest}.
246+
*
247+
* <p>
248+
* <b>Usage Example:</b>
249+
* <pre>
250+
* {@code
251+
* // Add a LoggingTransferListener to every transfer within the upload directory request
252+
* TransferRequestOverrideConfiguration transferConfiguration =
253+
* TransferRequestOverrideConfiguration.builder()
254+
* .addListener(LoggingTransferListener.create())
255+
* .build();
256+
*
257+
* UploadDirectoryRequest request =
258+
* UploadDirectoryRequest.builder()
259+
* .sourceDirectory(Paths.get("."))
260+
* .bucket("bucket")
261+
* .prefix("prefix")
262+
* .uploadRequestTransformer(uploadRequest -> uploadRequest.toBuilder()
263+
* .overrideConfiguration(transferConfiguration)
264+
* .build())
265+
* .build()
266+
*
267+
* UploadDirectoryTransfer uploadDirectory = transferManager.uploadDirectory(request);
268+
*
269+
* // Wait for the transfer to complete
270+
* CompletedUploadDirectory completedUploadDirectory = uploadDirectory.completionFuture().join();
271+
*
272+
* // Print out the failed uploads
273+
* completedUploadDirectory.failedUploads().forEach(System.out::println);
274+
* }
275+
* </pre>
276+
*
277+
* @param uploadRequestTransformer A transformer to use for modifying the file-level upload requests before execution
278+
* @return This builder for method chaining
279+
* @see UploadRequestTransformer
280+
*/
281+
Builder uploadRequestTransformer(UploadRequestTransformer uploadRequestTransformer);
282+
223283
/**
224284
* Add an optional request override configuration.
225285
*
@@ -249,13 +309,15 @@ default Builder overrideConfiguration(Consumer<UploadDirectoryOverrideConfigurat
249309
UploadDirectoryRequest build();
250310
}
251311

312+
252313
private static final class DefaultBuilder implements Builder {
253314

254315
private Path sourceDirectory;
255316
private String bucket;
256317
private String prefix;
257318
private UploadDirectoryOverrideConfiguration configuration;
258319
private String delimiter;
320+
private UploadRequestTransformer uploadRequestTransformer;
259321

260322
private DefaultBuilder() {
261323
}
@@ -265,6 +327,8 @@ private DefaultBuilder(UploadDirectoryRequest request) {
265327
this.bucket = request.bucket;
266328
this.prefix = request.prefix;
267329
this.configuration = request.overrideConfiguration;
330+
this.delimiter = request.delimiter;
331+
this.uploadRequestTransformer = request.uploadRequestTransformer;
268332
}
269333

270334
@Override
@@ -315,6 +379,12 @@ public Builder delimiter(String delimiter) {
315379
return this;
316380
}
317381

382+
@Override
383+
public Builder uploadRequestTransformer(UploadRequestTransformer uploadRequestTransformer) {
384+
this.uploadRequestTransformer = uploadRequestTransformer;
385+
return this;
386+
}
387+
318388
public void setDelimiter(String delimiter) {
319389
delimiter(delimiter);
320390
}

services-custom/s3-transfer-manager/src/main/java/software/amazon/awssdk/transfer/s3/UploadRequest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public static Class<? extends Builder> serializableBuilderClass() {
8787

8888
@Override
8989
public Builder toBuilder() {
90-
return new BuilderImpl();
90+
return new BuilderImpl(this);
9191
}
9292

9393
@Override
@@ -219,6 +219,15 @@ private static class BuilderImpl implements Builder {
219219
private Path source;
220220
private TransferRequestOverrideConfiguration configuration;
221221

222+
private BuilderImpl() {
223+
}
224+
225+
private BuilderImpl(UploadRequest uploadRequest) {
226+
this.source = uploadRequest.source;
227+
this.putObjectRequest = uploadRequest.putObjectRequest;
228+
this.configuration = uploadRequest.overrideConfiguration;
229+
}
230+
222231
@Override
223232
public Builder source(Path source) {
224233
this.source = source;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 software.amazon.awssdk.annotations.SdkPreviewApi;
19+
import software.amazon.awssdk.annotations.SdkPublicApi;
20+
import software.amazon.awssdk.annotations.ThreadSafe;
21+
22+
/**
23+
* A transformer for modifying {@link UploadRequest}s. This is commonly used as a functional interface in an upload request via
24+
* {@link UploadDirectoryRequest.Builder#uploadRequestTransformer(UploadRequestTransformer)}.
25+
*/
26+
@FunctionalInterface
27+
@ThreadSafe
28+
@SdkPublicApi
29+
@SdkPreviewApi
30+
public interface UploadRequestTransformer {
31+
/**
32+
* Transform an {@link UploadRequest}.
33+
*
34+
* @param originalRequest The request to transform
35+
* @return The transformed request.
36+
*/
37+
UploadRequest transform(UploadRequest originalRequest);
38+
39+
/**
40+
* Return an implementation of {@link UploadRequestTransformer} that returns the input request without modification.
41+
*/
42+
static UploadRequestTransformer identity() {
43+
return r -> r;
44+
}
45+
}

services-custom/s3-transfer-manager/src/main/java/software/amazon/awssdk/transfer/s3/internal/UploadDirectoryHelper.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import software.amazon.awssdk.transfer.s3.UploadDirectoryRequest;
4444
import software.amazon.awssdk.transfer.s3.UploadDirectoryTransfer;
4545
import software.amazon.awssdk.transfer.s3.UploadRequest;
46+
import software.amazon.awssdk.transfer.s3.UploadRequestTransformer;
4647
import software.amazon.awssdk.utils.CompletableFutureUtils;
4748
import software.amazon.awssdk.utils.Logger;
4849
import software.amazon.awssdk.utils.StringUtils;
@@ -230,9 +231,13 @@ private UploadRequest constructUploadRequest(UploadDirectoryRequest uploadDirect
230231
.bucket(uploadDirectoryRequest.bucket())
231232
.key(key)
232233
.build();
233-
return UploadRequest.builder()
234-
.source(path)
235-
.putObjectRequest(putObjectRequest)
236-
.build();
234+
235+
UploadRequestTransformer uploadRequestTransformer = uploadDirectoryRequest.uploadRequestTransformer()
236+
.orElse(UploadRequestTransformer.identity());
237+
238+
return uploadRequestTransformer.transform(UploadRequest.builder()
239+
.source(path)
240+
.putObjectRequest(putObjectRequest)
241+
.build());
237242
}
238243
}

0 commit comments

Comments
 (0)