Skip to content

Commit eade04e

Browse files
authored
[TM DownloadFile Pause and Resume] Part 1: Add configuration to enable overwriting existing files (#3125)
* Expose an option to overwrite an existing file in FileAsyncResponseTransformer * Add changelog entries and make TM use CREATE_OR_REPLACE_EXISTING write option by default * Address feedback * Update and address feedback
1 parent 456e7f3 commit eade04e

File tree

11 files changed

+576
-42
lines changed

11 files changed

+576
-42
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "AWS SDK for Java v2",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "Expose an option in `AsyncResponseTransformer#toFile` to allow overwriting and appending existing file."
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "S3 Transfer Manager (Develper Preview)",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "S3TransferManager#downloadFile now by default replaces existing file if it already exists instead of throwing FileAlreadyExistsException. See [#3108](https://github.com/aws/aws-sdk-java-v2/issues/3108)"
6+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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.core;
17+
18+
import java.nio.file.FileAlreadyExistsException;
19+
import java.nio.file.Path;
20+
import software.amazon.awssdk.annotations.SdkPublicApi;
21+
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
22+
import software.amazon.awssdk.utils.Validate;
23+
import software.amazon.awssdk.utils.builder.CopyableBuilder;
24+
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
25+
26+
/**
27+
* Configuration options for {@link AsyncResponseTransformer#toFile(Path, FileTransformerConfiguration)} to configure how the SDK
28+
* should write the file and if the SDK should delete the file when an exception occurs.
29+
*
30+
* @see #builder()
31+
* @see FileWriteOption
32+
* @see FailureBehavior
33+
*/
34+
@SdkPublicApi
35+
public final class FileTransformerConfiguration implements ToCopyableBuilder<FileTransformerConfiguration.Builder,
36+
FileTransformerConfiguration> {
37+
private final FileWriteOption fileWriteOption;
38+
private final FailureBehavior failureBehavior;
39+
40+
private FileTransformerConfiguration(DefaultBuilder builder) {
41+
this.fileWriteOption = Validate.paramNotNull(builder.fileWriteOption, "fileWriteOption");
42+
this.failureBehavior = Validate.paramNotNull(builder.failureBehavior, "failureBehavior");
43+
}
44+
45+
/**
46+
* The configured {@link FileWriteOption}
47+
*/
48+
public FileWriteOption fileWriteOption() {
49+
return fileWriteOption;
50+
}
51+
52+
/**
53+
* The configured {@link FailureBehavior}
54+
*/
55+
public FailureBehavior failureBehavior() {
56+
return failureBehavior;
57+
}
58+
59+
/**
60+
* Create a {@link Builder}, used to create a {@link FileTransformerConfiguration}.
61+
*/
62+
public static Builder builder() {
63+
return new DefaultBuilder();
64+
}
65+
66+
/**
67+
* Returns the default {@link FileTransformerConfiguration} for {@link FileWriteOption#CREATE_NEW}
68+
* <p>
69+
* Always create a new file. If the file already exists, {@link FileAlreadyExistsException} will be thrown.
70+
* In the event of an error, the SDK will attempt to delete the file (whatever has been written to it so far).
71+
*/
72+
public static FileTransformerConfiguration defaultCreateNew() {
73+
return builder().fileWriteOption(FileWriteOption.CREATE_NEW)
74+
.failureBehavior(FailureBehavior.DELETE)
75+
.build();
76+
}
77+
78+
/**
79+
* Returns the default {@link FileTransformerConfiguration} for {@link FileWriteOption#CREATE_OR_REPLACE_EXISTING}
80+
* <p>
81+
* Create a new file if it doesn't exist, otherwise replace the existing file.
82+
* In the event of an error, the SDK will attempt to delete the file (whatever has been written to it so far).
83+
*/
84+
public static FileTransformerConfiguration defaultCreateOrReplaceExisting() {
85+
return builder().fileWriteOption(FileWriteOption.CREATE_OR_REPLACE_EXISTING)
86+
.failureBehavior(FailureBehavior.DELETE)
87+
.build();
88+
}
89+
90+
/**
91+
* Returns the default {@link FileTransformerConfiguration} for {@link FileWriteOption#CREATE_OR_APPEND_EXISTING}
92+
* <p>
93+
* Create a new file if it doesn't exist, otherwise append to the existing file.
94+
* In the event of an error, the SDK will NOT attempt to delete the file, leaving it as-is
95+
*/
96+
public static FileTransformerConfiguration defaultCreateOrAppend() {
97+
return builder().fileWriteOption(FileWriteOption.CREATE_OR_APPEND_EXISTING)
98+
.failureBehavior(FailureBehavior.LEAVE)
99+
.build();
100+
}
101+
102+
@Override
103+
public Builder toBuilder() {
104+
return new DefaultBuilder().fileWriteOption(fileWriteOption)
105+
.failureBehavior(failureBehavior);
106+
}
107+
108+
@Override
109+
public boolean equals(Object o) {
110+
if (this == o) {
111+
return true;
112+
}
113+
if (o == null || getClass() != o.getClass()) {
114+
return false;
115+
}
116+
117+
FileTransformerConfiguration that = (FileTransformerConfiguration) o;
118+
119+
if (fileWriteOption != that.fileWriteOption) {
120+
return false;
121+
}
122+
return failureBehavior == that.failureBehavior;
123+
}
124+
125+
@Override
126+
public int hashCode() {
127+
int result = fileWriteOption != null ? fileWriteOption.hashCode() : 0;
128+
result = 31 * result + (failureBehavior != null ? failureBehavior.hashCode() : 0);
129+
return result;
130+
}
131+
132+
/**
133+
* Defines how the SDK should write the file
134+
*/
135+
public enum FileWriteOption {
136+
/**
137+
* Always create a new file. If the file already exists, {@link FileAlreadyExistsException} will be thrown.
138+
*/
139+
CREATE_NEW,
140+
141+
/**
142+
* Create a new file if it doesn't exist, otherwise replace the existing file.
143+
*/
144+
CREATE_OR_REPLACE_EXISTING,
145+
146+
/**
147+
* Create a new file if it doesn't exist, otherwise append to the existing file.
148+
*/
149+
CREATE_OR_APPEND_EXISTING
150+
}
151+
152+
/**
153+
* Defines how the SDK should handle the file if there is an exception
154+
*/
155+
public enum FailureBehavior {
156+
/**
157+
* In the event of an error, the SDK will attempt to delete the file (whatever has been written to it so far).
158+
*/
159+
DELETE,
160+
161+
/**
162+
* In the event of an error, the SDK will NOT attempt to delete the file and leave the file as-is (whatever has been
163+
* written to it so far)
164+
*/
165+
LEAVE
166+
}
167+
168+
public interface Builder extends CopyableBuilder<Builder, FileTransformerConfiguration> {
169+
170+
/**
171+
* Configures how to write the file
172+
*
173+
* @param fileWriteOption the file write option
174+
* @return This object for method chaining.
175+
*/
176+
Builder fileWriteOption(FileWriteOption fileWriteOption);
177+
178+
/**
179+
* Configures the {@link FailureBehavior} in the event of an error
180+
*
181+
* @param failureBehavior the failure behavior
182+
* @return This object for method chaining.
183+
*/
184+
Builder failureBehavior(FailureBehavior failureBehavior);
185+
}
186+
187+
private static class DefaultBuilder implements Builder {
188+
private FileWriteOption fileWriteOption;
189+
private FailureBehavior failureBehavior;
190+
191+
@Override
192+
public Builder fileWriteOption(FileWriteOption fileWriteOption) {
193+
this.fileWriteOption = fileWriteOption;
194+
return this;
195+
}
196+
197+
@Override
198+
public Builder failureBehavior(FailureBehavior failureBehavior) {
199+
this.failureBehavior = failureBehavior;
200+
return this;
201+
}
202+
203+
@Override
204+
public FileTransformerConfiguration build() {
205+
return new FileTransformerConfiguration(this);
206+
}
207+
}
208+
209+
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
import java.nio.ByteBuffer;
2020
import java.nio.file.Path;
2121
import java.util.concurrent.CompletableFuture;
22+
import java.util.function.Consumer;
2223
import software.amazon.awssdk.annotations.SdkPublicApi;
24+
import software.amazon.awssdk.core.FileTransformerConfiguration;
2325
import software.amazon.awssdk.core.ResponseBytes;
2426
import software.amazon.awssdk.core.SdkResponse;
2527
import software.amazon.awssdk.core.internal.async.ByteArrayAsyncResponseTransformer;
2628
import software.amazon.awssdk.core.internal.async.FileAsyncResponseTransformer;
2729
import software.amazon.awssdk.core.internal.async.PublisherAsyncResponseTransformer;
30+
import software.amazon.awssdk.utils.Validate;
2831

2932
/**
3033
* Callback interface to handle a streaming asynchronous response.
@@ -116,11 +119,38 @@ public interface AsyncResponseTransformer<ResponseT, ResultT> {
116119
* @param path Path to file to write to.
117120
* @param <ResponseT> Pojo Response type.
118121
* @return AsyncResponseTransformer instance.
122+
* @see #toFile(Path, FileTransformerConfiguration)
119123
*/
120124
static <ResponseT> AsyncResponseTransformer<ResponseT, ResponseT> toFile(Path path) {
121125
return new FileAsyncResponseTransformer<>(path);
122126
}
123127

128+
/**
129+
* Creates an {@link AsyncResponseTransformer} that writes all the content to the given file with the specified {@link
130+
* FileTransformerConfiguration}.
131+
*
132+
* @param path Path to file to write to.
133+
* @param config configuration for the transformer
134+
* @param <ResponseT> Pojo Response type.
135+
* @return AsyncResponseTransformer instance.
136+
* @see FileTransformerConfiguration
137+
*/
138+
static <ResponseT> AsyncResponseTransformer<ResponseT, ResponseT> toFile(Path path, FileTransformerConfiguration config) {
139+
return new FileAsyncResponseTransformer<>(path, config);
140+
}
141+
142+
/**
143+
* This is a convenience method that creates an instance of the {@link FileTransformerConfiguration} builder,
144+
* avoiding the need to create one manually via {@link FileTransformerConfiguration#builder()}.
145+
*
146+
* @see #toFile(Path, FileTransformerConfiguration)
147+
*/
148+
static <ResponseT> AsyncResponseTransformer<ResponseT, ResponseT> toFile(
149+
Path path, Consumer<FileTransformerConfiguration.Builder> config) {
150+
Validate.paramNotNull(config, "config");
151+
return new FileAsyncResponseTransformer<>(path, FileTransformerConfiguration.builder().applyMutation(config).build());
152+
}
153+
124154
/**
125155
* Creates an {@link AsyncResponseTransformer} that writes all the content to the given file. In the event of an error,
126156
* the SDK will attempt to delete the file (whatever has been written to it so far). If the file already exists, an
@@ -134,6 +164,34 @@ static <ResponseT> AsyncResponseTransformer<ResponseT, ResponseT> toFile(File fi
134164
return toFile(file.toPath());
135165
}
136166

167+
/**
168+
* Creates an {@link AsyncResponseTransformer} that writes all the content to the given file with the specified {@link
169+
* FileTransformerConfiguration}.
170+
*
171+
* @param file File to write to.
172+
* @param config configuration for the transformer
173+
* @param <ResponseT> Pojo Response type.
174+
* @return AsyncResponseTransformer instance.
175+
* @see FileTransformerConfiguration
176+
*/
177+
static <ResponseT> AsyncResponseTransformer<ResponseT, ResponseT> toFile(File file, FileTransformerConfiguration config) {
178+
return new FileAsyncResponseTransformer<>(file.toPath(), config);
179+
}
180+
181+
/**
182+
* This is a convenience method that creates an instance of the {@link FileTransformerConfiguration} builder,
183+
* avoiding the need to create one manually via {@link FileTransformerConfiguration#builder()}.
184+
*
185+
* @see #toFile(File, FileTransformerConfiguration)
186+
*/
187+
static <ResponseT> AsyncResponseTransformer<ResponseT, ResponseT> toFile(
188+
File file, Consumer<FileTransformerConfiguration.Builder> config) {
189+
Validate.paramNotNull(config, "config");
190+
return new FileAsyncResponseTransformer<>(file.toPath(), FileTransformerConfiguration.builder()
191+
.applyMutation(config)
192+
.build());
193+
}
194+
137195
/**
138196
* Creates an {@link AsyncResponseTransformer} that writes all content to a byte array.
139197
*

0 commit comments

Comments
 (0)