Skip to content

Commit 2aa5576

Browse files
author
J Plasmeier
committed
Revert "feat: Add S3CrtAsyncClient as MultipartPutobject (#90)"
This reverts commit 24be141.
1 parent 62953a6 commit 2aa5576

12 files changed

+705
-106
lines changed

pom.xml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,6 @@
7373
<optional>true</optional>
7474
</dependency>
7575

76-
<dependency>
77-
<groupId>software.amazon.awssdk.crt</groupId>
78-
<artifactId>aws-crt</artifactId>
79-
<version>0.21.5</version>
80-
<optional>true</optional>
81-
</dependency>
82-
8376
<dependency>
8477
<groupId>net.jcip</groupId>
8578
<artifactId>jcip-annotations</artifactId>

src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,18 @@
4040
public class S3AsyncEncryptionClient extends DelegatingS3AsyncClient {
4141

4242
private final S3AsyncClient _wrappedClient;
43-
private final S3AsyncClient _wrappedCrtClient;
4443
private final CryptographicMaterialsManager _cryptoMaterialsManager;
4544
private final SecureRandom _secureRandom;
4645
private final boolean _enableLegacyWrappingAlgorithms;
4746
private final boolean _enableLegacyUnauthenticatedModes;
4847
private final boolean _enableDelayedAuthenticationMode;
49-
private final boolean _enableMultipartPutObject;
5048

5149
private S3AsyncEncryptionClient(Builder builder) {
5250
super(builder._wrappedClient);
5351
_wrappedClient = builder._wrappedClient;
54-
_wrappedCrtClient = builder._wrappedCrtClient;
5552
_cryptoMaterialsManager = builder._cryptoMaterialsManager;
5653
_secureRandom = builder._secureRandom;
5754
_enableLegacyWrappingAlgorithms = builder._enableLegacyWrappingAlgorithms;
58-
_enableMultipartPutObject = builder._enableMultipartPutObject;
5955
_enableLegacyUnauthenticatedModes = builder._enableLegacyUnauthenticatedModes;
6056
_enableDelayedAuthenticationMode = builder._enableDelayedAuthenticationMode;
6157
}
@@ -75,8 +71,6 @@ public CompletableFuture<PutObjectResponse> putObject(PutObjectRequest putObject
7571
throws AwsServiceException, SdkClientException {
7672
PutEncryptedObjectPipeline pipeline = PutEncryptedObjectPipeline.builder()
7773
.s3AsyncClient(_wrappedClient)
78-
.crtClient(_wrappedCrtClient)
79-
.enableMultipartPutObject(_enableMultipartPutObject)
8074
.cryptoMaterialsManager(_cryptoMaterialsManager)
8175
.secureRandom(_secureRandom)
8276
.build();
@@ -133,16 +127,15 @@ public void close() {
133127
// TODO: The async / non-async clients can probably share a builder - revisit after implementing async
134128
public static class Builder {
135129
private S3AsyncClient _wrappedClient = S3AsyncClient.builder().build();
136-
private S3AsyncClient _wrappedCrtClient = null;
137130
private CryptographicMaterialsManager _cryptoMaterialsManager;
138131
private Keyring _keyring;
139132
private SecretKey _aesKey;
140133
private PartialRsaKeyPair _rsaKeyPair;
141134
private String _kmsKeyId;
142135
private boolean _enableLegacyWrappingAlgorithms = false;
143136
private boolean _enableLegacyUnauthenticatedModes = false;
144-
private boolean _enableMultipartPutObject = false;
145137
private boolean _enableDelayedAuthenticationMode = false;
138+
private boolean _enableMultipartPutObject = false;
146139
private Provider _cryptoProvider = null;
147140
private SecureRandom _secureRandom = new SecureRandom();
148141

@@ -158,8 +151,7 @@ public Builder wrappedClient(S3AsyncClient wrappedClient) {
158151
if (wrappedClient instanceof S3AsyncEncryptionClient) {
159152
throw new S3EncryptionClientException("Cannot use S3EncryptionClient as wrapped client");
160153
}
161-
// Initializes only when wrappedAsyncClient is configured by user.
162-
this._wrappedCrtClient = wrappedClient;
154+
163155
this._wrappedClient = wrappedClient;
164156
return this;
165157
}
@@ -246,7 +238,9 @@ public Builder enableDelayedAuthenticationMode(boolean shouldEnableDelayedAuthen
246238
}
247239

248240
public Builder enableMultipartPutObject(boolean _enableMultipartPutObject) {
249-
this._enableMultipartPutObject = _enableMultipartPutObject;
241+
if (_enableMultipartPutObject) {
242+
throw new S3EncryptionClientException("Async multipart PutObject is currently disabled.");
243+
}
250244
return this;
251245
}
252246

src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java

Lines changed: 122 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import software.amazon.awssdk.services.s3.model.AbortMultipartUploadResponse;
1919
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest;
2020
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadResponse;
21+
import software.amazon.awssdk.services.s3.model.CompletedPart;
2122
import software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest;
2223
import software.amazon.awssdk.services.s3.model.CreateMultipartUploadResponse;
2324
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
@@ -32,25 +33,33 @@
3233
import software.amazon.awssdk.services.s3.model.UploadPartRequest;
3334
import software.amazon.awssdk.services.s3.model.UploadPartResponse;
3435
import software.amazon.encryption.s3.internal.GetEncryptedObjectPipeline;
36+
import software.amazon.encryption.s3.internal.MultiFileOutputStream;
3537
import software.amazon.encryption.s3.internal.MultipartUploadObjectPipeline;
3638
import software.amazon.encryption.s3.internal.PutEncryptedObjectPipeline;
39+
import software.amazon.encryption.s3.internal.UploadObjectObserver;
3740
import software.amazon.encryption.s3.materials.AesKeyring;
3841
import software.amazon.encryption.s3.materials.CryptographicMaterialsManager;
3942
import software.amazon.encryption.s3.materials.DefaultCryptoMaterialsManager;
4043
import software.amazon.encryption.s3.materials.Keyring;
4144
import software.amazon.encryption.s3.materials.KmsKeyring;
45+
import software.amazon.encryption.s3.materials.MultipartConfiguration;
4246
import software.amazon.encryption.s3.materials.PartialRsaKeyPair;
4347
import software.amazon.encryption.s3.materials.RsaKeyring;
4448

4549
import javax.crypto.SecretKey;
50+
import java.io.IOException;
4651
import java.security.KeyPair;
4752
import java.security.Provider;
4853
import java.security.SecureRandom;
54+
import java.util.ArrayList;
4955
import java.util.List;
5056
import java.util.Map;
5157
import java.util.concurrent.CompletableFuture;
5258
import java.util.concurrent.CompletionException;
59+
import java.util.concurrent.ExecutionException;
60+
import java.util.concurrent.ExecutorService;
5361
import java.util.concurrent.Executors;
62+
import java.util.concurrent.Future;
5463
import java.util.function.Consumer;
5564

5665
import static software.amazon.encryption.s3.S3EncryptionClientUtilities.INSTRUCTION_FILE_SUFFIX;
@@ -64,12 +73,13 @@ public class S3EncryptionClient extends DelegatingS3Client {
6473

6574
// Used for request-scoped encryption contexts for supporting keys
6675
public static final ExecutionAttribute<Map<String, String>> ENCRYPTION_CONTEXT = new ExecutionAttribute<>("EncryptionContext");
76+
public static final ExecutionAttribute<MultipartConfiguration> CONFIGURATION = new ExecutionAttribute<>("MultipartConfiguration");
6777
// TODO: Replace with UploadPartRequest.isLastPart() when launched.
6878
// Used for multipart uploads
6979
public static final ExecutionAttribute<Boolean> IS_LAST_PART = new ExecutionAttribute<>("isLastPart");
7080

71-
private final S3AsyncClient _wrappedClient;
72-
private final S3AsyncClient _wrappedCrtClient;
81+
private final S3Client _wrappedClient;
82+
private final S3AsyncClient _wrappedAsyncClient;
7383
private final CryptographicMaterialsManager _cryptoMaterialsManager;
7484
private final SecureRandom _secureRandom;
7585
private final boolean _enableLegacyWrappingAlgorithms;
@@ -79,11 +89,9 @@ public class S3EncryptionClient extends DelegatingS3Client {
7989
private final MultipartUploadObjectPipeline _multipartPipeline;
8090

8191
private S3EncryptionClient(Builder builder) {
82-
// The non-encrypted APIs will use a default client.
83-
// In the future, we may want to make this configurable.
84-
super(S3Client.create());
92+
super(builder._wrappedClient);
8593
_wrappedClient = builder._wrappedClient;
86-
_wrappedCrtClient = builder._wrappedCrtClient;
94+
_wrappedAsyncClient = builder._wrappedAsyncClient;
8795
_cryptoMaterialsManager = builder._cryptoMaterialsManager;
8896
_secureRandom = builder._secureRandom;
8997
_enableLegacyWrappingAlgorithms = builder._enableLegacyWrappingAlgorithms;
@@ -103,6 +111,13 @@ public static Consumer<AwsRequestOverrideConfiguration.Builder> withAdditionalCo
103111
builder.putExecutionAttribute(S3EncryptionClient.ENCRYPTION_CONTEXT, encryptionContext);
104112
}
105113

114+
// Helper function to attach encryption contexts to a request
115+
public static Consumer<AwsRequestOverrideConfiguration.Builder> withAdditionalConfiguration(Map<String, String> encryptionContext, MultipartConfiguration multipartConfiguration) {
116+
return builder ->
117+
builder.putExecutionAttribute(S3EncryptionClient.ENCRYPTION_CONTEXT, encryptionContext)
118+
.putExecutionAttribute(S3EncryptionClient.CONFIGURATION, multipartConfiguration);
119+
}
120+
106121
// Helper function to determine last upload part during multipart uploads
107122
public static Consumer<AwsRequestOverrideConfiguration.Builder> isLastPart(Boolean isLastPart) {
108123
return builder ->
@@ -113,11 +128,21 @@ public static Consumer<AwsRequestOverrideConfiguration.Builder> isLastPart(Boole
113128
public PutObjectResponse putObject(PutObjectRequest putObjectRequest, RequestBody requestBody)
114129
throws AwsServiceException, SdkClientException {
115130

131+
if (_enableMultipartPutObject) {
132+
try {
133+
// TODO: Confirm best way to wrap CompleteMultipartUploadResponse with PutObjectResponse
134+
CompleteMultipartUploadResponse completeResponse = multipartPutObject(putObjectRequest, requestBody);
135+
PutObjectResponse response = PutObjectResponse.builder()
136+
.eTag(completeResponse.eTag())
137+
.build();
138+
return response;
139+
} catch (Throwable e) {
140+
throw new S3EncryptionClientException("Exception while performing Multipart Upload PutObject", e);
141+
}
142+
}
116143
PutEncryptedObjectPipeline pipeline = PutEncryptedObjectPipeline.builder()
117-
.s3AsyncClient(_wrappedClient)
118-
.crtClient(_wrappedCrtClient)
144+
.s3AsyncClient(_wrappedAsyncClient)
119145
.cryptoMaterialsManager(_cryptoMaterialsManager)
120-
.enableMultipartPutObject(_enableMultipartPutObject)
121146
.secureRandom(_secureRandom)
122147
.build();
123148

@@ -131,7 +156,7 @@ public <T> T getObject(GetObjectRequest getObjectRequest,
131156
throws AwsServiceException, SdkClientException {
132157

133158
GetEncryptedObjectPipeline pipeline = GetEncryptedObjectPipeline.builder()
134-
.s3AsyncClient(_wrappedClient)
159+
.s3AsyncClient(_wrappedAsyncClient)
135160
.cryptoMaterialsManager(_cryptoMaterialsManager)
136161
.enableLegacyWrappingAlgorithms(_enableLegacyWrappingAlgorithms)
137162
.enableLegacyUnauthenticatedModes(_enableLegacyUnauthenticatedModes)
@@ -148,14 +173,76 @@ public <T> T getObject(GetObjectRequest getObjectRequest,
148173
}
149174
}
150175

176+
private CompleteMultipartUploadResponse multipartPutObject(PutObjectRequest request, RequestBody requestBody) throws Throwable {
177+
178+
AwsRequestOverrideConfiguration overrideConfig = request.overrideConfiguration().get();
179+
// If MultipartConfiguration is null, Initialize MultipartConfiguration
180+
MultipartConfiguration multipartConfiguration = overrideConfig
181+
.executionAttributes()
182+
.getOptionalAttribute(S3EncryptionClient.CONFIGURATION)
183+
.orElse(MultipartConfiguration.builder().build());
184+
185+
ExecutorService es = multipartConfiguration.executorService();
186+
final boolean defaultExecutorService = es == null;
187+
if (es == null) {
188+
throw new S3EncryptionClientException("ExecutorService should not be null, Please initialize during MultipartConfiguration");
189+
}
190+
191+
UploadObjectObserver observer = multipartConfiguration.uploadObjectObserver();
192+
if (observer == null) {
193+
throw new S3EncryptionClientException("UploadObjectObserver should not be null, Please initialize during MultipartConfiguration");
194+
}
195+
196+
observer.init(request, _wrappedAsyncClient, this, es);
197+
final String uploadId = observer.onUploadCreation(request);
198+
final List<CompletedPart> partETags = new ArrayList<>();
199+
200+
MultiFileOutputStream outputStream = multipartConfiguration.multiFileOutputStream();
201+
if (outputStream == null) {
202+
throw new S3EncryptionClientException("MultiFileOutputStream should not be null, Please initialize during MultipartConfiguration");
203+
}
204+
205+
try {
206+
// initialize the multi-file output stream
207+
outputStream.init(observer, multipartConfiguration.partSize(), multipartConfiguration.diskLimit());
208+
// Kicks off the encryption-upload pipeline;
209+
// Note outputStream is automatically closed upon method completion.
210+
_multipartPipeline.putLocalObject(requestBody, uploadId, outputStream);
211+
// block till all part have been uploaded
212+
for (Future<Map<Integer, UploadPartResponse>> future : observer.futures()) {
213+
Map<Integer, UploadPartResponse> partResponseMap = future.get();
214+
partResponseMap.forEach((partNumber, uploadPartResponse) -> partETags.add(CompletedPart.builder()
215+
.partNumber(partNumber)
216+
.eTag(uploadPartResponse.eTag())
217+
.build()));
218+
}
219+
} catch (IOException | InterruptedException | ExecutionException | RuntimeException | Error ex) {
220+
throw onAbort(observer, ex);
221+
} finally {
222+
if (defaultExecutorService) {
223+
// shut down the locally created thread pool
224+
es.shutdownNow();
225+
}
226+
// delete left-over temp files
227+
outputStream.cleanup();
228+
}
229+
// Complete upload
230+
return observer.onCompletion(partETags);
231+
}
232+
233+
private <T extends Throwable> T onAbort(UploadObjectObserver observer, T t) {
234+
observer.onAbort();
235+
return t;
236+
}
237+
151238
@Override
152239
public DeleteObjectResponse deleteObject(DeleteObjectRequest deleteObjectRequest) throws AwsServiceException,
153240
SdkClientException {
154241
// Delete the object
155-
DeleteObjectResponse deleteObjectResponse = _wrappedClient.deleteObject(deleteObjectRequest).join();
242+
DeleteObjectResponse deleteObjectResponse = _wrappedAsyncClient.deleteObject(deleteObjectRequest).join();
156243
// If Instruction file exists, delete the instruction file as well.
157244
String instructionObjectKey = deleteObjectRequest.key() + INSTRUCTION_FILE_SUFFIX;
158-
_wrappedClient.deleteObject(builder -> builder
245+
_wrappedAsyncClient.deleteObject(builder -> builder
159246
.bucket(deleteObjectRequest.bucket())
160247
.key(instructionObjectKey)).join();
161248
return deleteObjectResponse;
@@ -165,10 +252,10 @@ public DeleteObjectResponse deleteObject(DeleteObjectRequest deleteObjectRequest
165252
public DeleteObjectsResponse deleteObjects(DeleteObjectsRequest deleteObjectsRequest) throws AwsServiceException,
166253
SdkClientException {
167254
// Delete the objects
168-
DeleteObjectsResponse deleteObjectsResponse = _wrappedClient.deleteObjects(deleteObjectsRequest).join();
255+
DeleteObjectsResponse deleteObjectsResponse = _wrappedAsyncClient.deleteObjects(deleteObjectsRequest).join();
169256
// If Instruction files exists, delete the instruction files as well.
170257
List<ObjectIdentifier> deleteObjects = instructionFileKeysToDelete(deleteObjectsRequest);
171-
_wrappedClient.deleteObjects(DeleteObjectsRequest.builder()
258+
_wrappedAsyncClient.deleteObjects(DeleteObjectsRequest.builder()
172259
.bucket(deleteObjectsRequest.bucket())
173260
.delete(builder -> builder.objects(deleteObjects))
174261
.build()).join();
@@ -213,12 +300,14 @@ public AbortMultipartUploadResponse abortMultipartUpload(AbortMultipartUploadReq
213300

214301
@Override
215302
public void close() {
216-
_wrappedClient.close();
303+
_wrappedAsyncClient.close();
217304
}
218305

219306
public static class Builder {
220-
private S3AsyncClient _wrappedClient = S3AsyncClient.create();
221-
private S3AsyncClient _wrappedCrtClient = null;
307+
// The non-encrypted APIs will use a default client.
308+
// In the future, we may want to make this configurable.
309+
private S3Client _wrappedClient = S3Client.create();
310+
private S3AsyncClient _wrappedAsyncClient = S3AsyncClient.create();
222311

223312
private MultipartUploadObjectPipeline _multipartPipeline;
224313
private CryptographicMaterialsManager _cryptoMaterialsManager;
@@ -241,13 +330,22 @@ private Builder() {
241330
* S3AsyncClient will be reflected in this Builder.
242331
*/
243332
@SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Pass mutability into wrapping client")
244-
public Builder wrappedClient(S3AsyncClient wrappedClient) {
245-
if (wrappedClient instanceof S3AsyncEncryptionClient) {
246-
throw new S3EncryptionClientException("Cannot use S3AsyncEncryptionClient as wrapped client");
333+
public Builder wrappedAsyncClient(S3AsyncClient _wrappedAsyncClient) {
334+
if (_wrappedAsyncClient instanceof S3AsyncEncryptionClient) {
335+
throw new S3EncryptionClientException("Cannot use S3EncryptionClient as wrapped client");
336+
}
337+
338+
this._wrappedAsyncClient = _wrappedAsyncClient;
339+
return this;
340+
}
341+
342+
@SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Pass mutability into wrapping client")
343+
public Builder _wrappedAsyncClient(S3AsyncClient _wrappedAsyncClient) {
344+
if (_wrappedAsyncClient instanceof S3AsyncEncryptionClient) {
345+
throw new S3EncryptionClientException("Cannot use S3EncryptionClient as wrapped client");
247346
}
248-
// Initializes only when wrappedClient is configured by user.
249-
this._wrappedCrtClient = wrappedClient;
250-
this._wrappedClient = wrappedClient;
347+
348+
this._wrappedAsyncClient = _wrappedAsyncClient;
251349
return this;
252350
}
253351

@@ -385,7 +483,7 @@ public S3EncryptionClient build() {
385483
}
386484

387485
_multipartPipeline = MultipartUploadObjectPipeline.builder()
388-
.s3AsyncClient(_wrappedClient)
486+
.s3AsyncClient(_wrappedAsyncClient)
389487
.cryptoMaterialsManager(_cryptoMaterialsManager)
390488
.secureRandom(_secureRandom)
391489
.build();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package software.amazon.encryption.s3.internal;
2+
3+
/**
4+
* A file deletion event.
5+
*/
6+
public class FileDeletionEvent {
7+
// currently only a placeholder so method signature won't be affected by
8+
// future changes
9+
}

0 commit comments

Comments
 (0)