Skip to content

Commit 09e3512

Browse files
authored
Merge branch 'main' into objectlockConformance
2 parents e1107d9 + 03f6a8c commit 09e3512

File tree

153 files changed

+6474
-803
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

153 files changed

+6474
-803
lines changed

blob/blob-ali/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<dependency>
2424
<groupId>org.wiremock</groupId>
2525
<artifactId>wiremock</artifactId>
26-
<version>3.13.2</version>
26+
<version>3.12.1</version>
2727
<scope>test</scope>
2828
</dependency>
2929
<dependency>

blob/blob-aws/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<dependency>
1919
<groupId>org.wiremock</groupId>
2020
<artifactId>wiremock</artifactId>
21-
<version>3.13.2</version>
21+
<version>3.12.1</version>
2222
<scope>test</scope>
2323
</dependency>
2424
<dependency>

blob/blob-aws/src/main/java/com/salesforce/multicloudj/blob/aws/AwsTransformer.java

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import java.util.Map;
4545
import java.util.concurrent.Executors;
4646
import java.util.stream.Collectors;
47+
import org.apache.commons.lang3.StringUtils;
4748
import software.amazon.awssdk.core.ResponseInputStream;
4849
import software.amazon.awssdk.core.async.AsyncRequestBody;
4950
import software.amazon.awssdk.retries.StandardRetryStrategy;
@@ -420,32 +421,44 @@ public CreateMultipartUploadRequest toCreateMultipartUploadRequest(
420421
}
421422
// else: no SSE headers; S3 uses bucket default encryption
422423

424+
if (request.isChecksumEnabled()) {
425+
builder.checksumAlgorithm(CRC32_C);
426+
}
427+
423428
return builder.build();
424429
}
425430

426431
public UploadPartRequest toUploadPartRequest(MultipartUpload mpu, MultipartPart mpp) {
427-
return UploadPartRequest.builder()
432+
UploadPartRequest.Builder builder = UploadPartRequest.builder()
428433
.bucket(getBucket())
429434
.key(mpu.getKey())
430435
.uploadId(mpu.getId())
431436
.partNumber(mpp.getPartNumber())
432-
.contentLength(mpp.getContentLength())
433-
.build();
437+
.contentLength(mpp.getContentLength());
438+
439+
if (!StringUtils.isEmpty(mpp.getChecksumValue())) {
440+
builder.checksumAlgorithm(CRC32_C);
441+
builder.checksumCRC32C(mpp.getChecksumValue());
442+
}
443+
444+
return builder.build();
434445
}
435446

436447
public CompleteMultipartUploadRequest toCompleteMultipartUploadRequest(
437448
MultipartUpload mpu, List<UploadPartResponse> parts) {
438449

439-
List<CompletedPart> completedParts =
440-
parts.stream()
441-
.sorted(Comparator.comparingInt(UploadPartResponse::getPartNumber))
442-
.map(
443-
part ->
444-
CompletedPart.builder()
445-
.partNumber(part.getPartNumber())
446-
.eTag(part.getEtag())
447-
.build())
448-
.collect(Collectors.toList());
450+
List<CompletedPart> completedParts = parts.stream()
451+
.sorted(Comparator.comparingInt(UploadPartResponse::getPartNumber))
452+
.map(part -> {
453+
CompletedPart.Builder partBuilder = CompletedPart.builder()
454+
.partNumber(part.getPartNumber())
455+
.eTag(part.getEtag());
456+
if (StringUtils.isNotEmpty(part.getChecksumValue())) {
457+
partBuilder.checksumCRC32C(part.getChecksumValue());
458+
}
459+
return partBuilder.build();
460+
})
461+
.collect(Collectors.toList());
449462

450463
return CompleteMultipartUploadRequest.builder()
451464
.bucket(getBucket())
@@ -508,11 +521,14 @@ public PutObjectPresignRequest toPutObjectPresignRequest(PresignedUrlRequest req
508521
}
509522

510523
public GetObjectPresignRequest toGetObjectPresignRequest(PresignedUrlRequest request) {
511-
GetObjectRequest getObjectRequest =
512-
GetObjectRequest.builder().bucket(getBucket()).key(request.getKey()).build();
524+
GetObjectRequest.Builder getObjectBuilder =
525+
GetObjectRequest.builder().bucket(getBucket()).key(request.getKey());
526+
if (request.getContentDisposition() != null) {
527+
getObjectBuilder.responseContentDisposition(request.getContentDisposition());
528+
}
513529
return GetObjectPresignRequest.builder()
514530
.signatureDuration(request.getDuration())
515-
.getObjectRequest(getObjectRequest)
531+
.getObjectRequest(getObjectBuilder.build())
516532
.build();
517533
}
518534

@@ -642,26 +658,33 @@ public CopyResponse toCopyResponse(String destKey, CopyObjectResponse response)
642658
.build();
643659
}
644660

645-
public MultipartUpload toMultipartUpload(
646-
MultipartUploadRequest request, CreateMultipartUploadResponse response) {
661+
public MultipartUpload toMultipartUpload(MultipartUploadRequest request,
662+
CreateMultipartUploadResponse response) {
647663
return MultipartUpload.builder()
648664
.bucket(response.bucket())
649665
.key(response.key())
650666
.id(response.uploadId())
651667
.metadata(request.getMetadata())
652668
.tags(request.getTags())
653669
.kmsKeyId(request.getKmsKeyId())
670+
.checksumEnabled(request.isChecksumEnabled())
654671
.build();
655672
}
656673

657674
public UploadPartResponse toUploadPartResponse(
658675
MultipartPart part, software.amazon.awssdk.services.s3.model.UploadPartResponse response) {
659-
return new UploadPartResponse(part.getPartNumber(), response.eTag(), part.getContentLength());
676+
return new UploadPartResponse(
677+
part.getPartNumber(), response.eTag(), part.getContentLength(),
678+
response.checksumCRC32C());
660679
}
661680

662681
public MultipartUploadResponse toMultipartUploadResponse(
663682
CompleteMultipartUploadResponse response) {
664-
return new MultipartUploadResponse(response.eTag());
683+
String checksumValue = null;
684+
if (response.checksumCRC32C() != null) {
685+
checksumValue = response.checksumCRC32C();
686+
}
687+
return new MultipartUploadResponse(response.eTag(), checksumValue);
665688
}
666689

667690
/**

blob/blob-aws/src/test/java/com/salesforce/multicloudj/blob/aws/AwsBlobStoreTest.java

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
import software.amazon.awssdk.services.s3.S3ClientBuilder;
8787
import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest;
8888
import software.amazon.awssdk.services.s3.model.AbortMultipartUploadResponse;
89+
import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm;
8990
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest;
9091
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadResponse;
9192
import software.amazon.awssdk.services.s3.model.CompletedPart;
@@ -1097,13 +1098,82 @@ void testDoAbortMultipartUpload() {
10971098
assertEquals("mpu-id", actualRequest.uploadId());
10981099
}
10991100

1101+
@Test
1102+
void testMultipartUploadWithChecksum() {
1103+
// Step 1: Initiate multipart upload with checksum enabled
1104+
CreateMultipartUploadResponse mockCreateResponse = mock(CreateMultipartUploadResponse.class);
1105+
doReturn("bucket-1").when(mockCreateResponse).bucket();
1106+
doReturn("object-1").when(mockCreateResponse).key();
1107+
doReturn("mpu-id").when(mockCreateResponse).uploadId();
1108+
when(mockS3Client.createMultipartUpload((CreateMultipartUploadRequest) any())).thenReturn(
1109+
mockCreateResponse);
1110+
1111+
MultipartUploadRequest request = new MultipartUploadRequest.Builder()
1112+
.withKey("object-1")
1113+
.withChecksumEnabled(true)
1114+
.build();
1115+
1116+
MultipartUpload mpu = aws.initiateMultipartUpload(request);
1117+
1118+
// Verify checksumAlgorithm is set on create request
1119+
ArgumentCaptor<CreateMultipartUploadRequest> createCaptor =
1120+
ArgumentCaptor.forClass(CreateMultipartUploadRequest.class);
1121+
verify(mockS3Client, times(1)).createMultipartUpload(createCaptor.capture());
1122+
assertEquals(ChecksumAlgorithm.CRC32_C, createCaptor.getValue().checksumAlgorithm());
1123+
assertTrue(mpu.isChecksumEnabled());
1124+
1125+
// Step 2: Upload part with checksum
1126+
UploadPartResponse mockUploadPartResponse = mock(UploadPartResponse.class);
1127+
doReturn("part-etag").when(mockUploadPartResponse).eTag();
1128+
doReturn("AAAAAA==").when(mockUploadPartResponse).checksumCRC32C();
1129+
doReturn(mockUploadPartResponse).when(mockS3Client)
1130+
.uploadPart(any(UploadPartRequest.class), any(RequestBody.class));
1131+
1132+
byte[] content = "This is test data".getBytes(StandardCharsets.UTF_8);
1133+
MultipartPart part = new MultipartPart(1, content, "AAAAAA==");
1134+
1135+
var uploadPartResp = aws.uploadMultipartPart(mpu, part);
1136+
1137+
// Verify checksumCRC32C is set on upload part request
1138+
ArgumentCaptor<UploadPartRequest> uploadCaptor =
1139+
ArgumentCaptor.forClass(UploadPartRequest.class);
1140+
verify(mockS3Client, times(1)).uploadPart(uploadCaptor.capture(), any(RequestBody.class));
1141+
assertEquals(ChecksumAlgorithm.CRC32_C, uploadCaptor.getValue().checksumAlgorithm());
1142+
assertEquals("AAAAAA==", uploadCaptor.getValue().checksumCRC32C());
1143+
1144+
// Verify checksum in upload part response
1145+
assertEquals("AAAAAA==", uploadPartResp.getChecksumValue());
1146+
1147+
// Step 3: Complete multipart upload with checksum
1148+
CompleteMultipartUploadResponse mockCompleteResponse =
1149+
mock(CompleteMultipartUploadResponse.class);
1150+
doReturn("complete-etag").when(mockCompleteResponse).eTag();
1151+
doReturn("composite-checksum==").when(mockCompleteResponse).checksumCRC32C();
1152+
doReturn(mockCompleteResponse).when(mockS3Client)
1153+
.completeMultipartUpload((CompleteMultipartUploadRequest) any());
1154+
1155+
List<com.salesforce.multicloudj.blob.driver.UploadPartResponse> parts =
1156+
List.of(new com.salesforce.multicloudj.blob.driver.UploadPartResponse(1, "part-etag",
1157+
content.length, "AAAAAA=="));
1158+
1159+
MultipartUploadResponse completeResp = aws.completeMultipartUpload(mpu, parts);
1160+
1161+
// Verify checksumCRC32C is set on completed part
1162+
ArgumentCaptor<CompleteMultipartUploadRequest> completeCaptor =
1163+
ArgumentCaptor.forClass(CompleteMultipartUploadRequest.class);
1164+
verify(mockS3Client, times(1)).completeMultipartUpload(completeCaptor.capture());
1165+
List<CompletedPart> completedParts = completeCaptor.getValue().multipartUpload().parts();
1166+
assertEquals("AAAAAA==", completedParts.get(0).checksumCRC32C());
1167+
1168+
// Verify composite checksum in response
1169+
assertEquals("composite-checksum==", completeResp.getChecksumValue());
1170+
}
1171+
11001172
@Test
11011173
void testDoGetTags() {
11021174
GetObjectTaggingResponse mockResponse = mock(GetObjectTaggingResponse.class);
1103-
List<Tag> tags =
1104-
List.of(
1105-
Tag.builder().key("key1").value("value1").build(),
1106-
Tag.builder().key("key2").value("value2").build());
1175+
List<Tag> tags = List.of(Tag.builder().key("key1").value("value1").build(),
1176+
Tag.builder().key("key2").value("value2").build());
11071177
doReturn(tags).when(mockResponse).tagSet();
11081178
doReturn(mockResponse).when(mockS3Client).getObjectTagging((GetObjectTaggingRequest) any());
11091179

@@ -1213,7 +1283,7 @@ void testDoDoesObjectExist() {
12131283
void testDoDoesBucketExist() {
12141284
HeadBucketResponse mockResponse = mock(HeadBucketResponse.class);
12151285
when(mockS3Client.headBucket(
1216-
ArgumentMatchers.<java.util.function.Consumer<HeadBucketRequest.Builder>>any()))
1286+
ArgumentMatchers.<java.util.function.Consumer<HeadBucketRequest.Builder>>any()))
12171287
.thenReturn(mockResponse);
12181288

12191289
boolean result = aws.doDoesBucketExist();

blob/blob-aws/src/test/java/com/salesforce/multicloudj/blob/aws/AwsTransformerTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,43 @@ void testToGetObjectPresignRequest() {
587587
assertEquals(BUCKET, actualRequest.getObjectRequest().bucket());
588588
assertEquals("object-1", actualRequest.getObjectRequest().key());
589589
assertEquals(Duration.ofHours(4), actualRequest.signatureDuration());
590+
assertNull(actualRequest.getObjectRequest().responseContentDisposition());
591+
}
592+
593+
@Test
594+
void testToGetObjectPresignRequest_WithContentDisposition() {
595+
PresignedUrlRequest presignedUrlRequest =
596+
PresignedUrlRequest.builder()
597+
.type(PresignedOperation.DOWNLOAD)
598+
.key("object-1")
599+
.duration(Duration.ofHours(4))
600+
.contentDisposition("attachment; filename=\"report.pdf\"")
601+
.build();
602+
GetObjectPresignRequest actualRequest =
603+
transformer.toGetObjectPresignRequest(presignedUrlRequest);
604+
assertEquals(BUCKET, actualRequest.getObjectRequest().bucket());
605+
assertEquals("object-1", actualRequest.getObjectRequest().key());
606+
assertEquals(Duration.ofHours(4), actualRequest.signatureDuration());
607+
assertEquals(
608+
"attachment; filename=\"report.pdf\"",
609+
actualRequest.getObjectRequest().responseContentDisposition());
610+
}
611+
612+
@Test
613+
void testToPutObjectPresignRequest_IgnoresContentDisposition() {
614+
PresignedUrlRequest presignedUrlRequest =
615+
PresignedUrlRequest.builder()
616+
.type(PresignedOperation.UPLOAD)
617+
.key("object-1")
618+
.duration(Duration.ofHours(4))
619+
.contentDisposition("attachment; filename=\"report.pdf\"")
620+
.build();
621+
PutObjectPresignRequest actualRequest =
622+
transformer.toPutObjectPresignRequest(presignedUrlRequest);
623+
assertEquals(BUCKET, actualRequest.putObjectRequest().bucket());
624+
assertEquals("object-1", actualRequest.putObjectRequest().key());
625+
assertEquals(Duration.ofHours(4), actualRequest.signatureDuration());
626+
assertNull(actualRequest.putObjectRequest().contentDisposition());
590627
}
591628

592629
@Test
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"id" : "a779b0a1-022c-450c-bd6f-5e6041047169",
3+
"name" : "AwsBlobStoreIT_testMultipartUpload_withChecksum-DELETE-0",
4+
"request" : {
5+
"url" : "/chameleon-jcloud/conformance-tests/multipart-withChecksum",
6+
"method" : "DELETE"
7+
},
8+
"response" : {
9+
"status" : 204,
10+
"headers" : {
11+
"Server" : "AmazonS3",
12+
"x-amz-request-id" : "DDK10V547NXC6TPY",
13+
"x-amz-id-2" : "4Pt7gMty3ZC7zWWnuq+ghaHDRu1Q4k25buEL6aEakFwESV2B91gsx6WUWqA9c+TiaHdxH1o3kO8=",
14+
"Date" : "Mon, 09 Mar 2026 18:31:47 GMT"
15+
}
16+
},
17+
"uuid" : "a779b0a1-022c-450c-bd6f-5e6041047169",
18+
"persistent" : true,
19+
"insertionIndex" : 598
20+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"id" : "f794524f-b1d5-4b37-ad36-0f7ad79b3638",
3+
"name" : "AwsBlobStoreIT_testMultipartUpload_withChecksum-HEAD-1",
4+
"request" : {
5+
"url" : "/chameleon-jcloud/conformance-tests/multipart-withChecksum",
6+
"method" : "HEAD"
7+
},
8+
"response" : {
9+
"status" : 200,
10+
"headers" : {
11+
"Accept-Ranges" : "bytes",
12+
"x-amz-meta-key1" : "value1",
13+
"Server" : "AmazonS3",
14+
"ETag" : "\"63a814bc1f352d8149b1dd9f3747e89e-2\"",
15+
"Last-Modified" : "Mon, 09 Mar 2026 18:31:37 GMT",
16+
"x-amz-request-id" : "DDK6G2ZVBH6SKR9J",
17+
"x-amz-server-side-encryption" : "AES256",
18+
"x-amz-id-2" : "u4rU0eGuH02xa5BG6xwucQYz6+b3B4KUkYo2YG76foWuyQWOpIdFE4tLhn89cQ1Za/l9ROPb8ZY=",
19+
"Date" : "Mon, 09 Mar 2026 18:31:47 GMT",
20+
"Content-Type" : "binary/octet-stream"
21+
}
22+
},
23+
"uuid" : "f794524f-b1d5-4b37-ad36-0f7ad79b3638",
24+
"persistent" : true,
25+
"insertionIndex" : 599
26+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"id" : "95d09e13-e409-431c-bb5b-578818b95b47",
3+
"name" : "AwsBlobStoreIT_testMultipartUpload_withChecksum-POST-2",
4+
"request" : {
5+
"urlPath" : "/chameleon-jcloud/conformance-tests/multipart-withChecksum",
6+
"method" : "POST",
7+
"headers" : {
8+
"X-Query-Param-Count" : {
9+
"equalTo" : "1"
10+
}
11+
},
12+
"queryParameters" : {
13+
"uploadId" : {
14+
"hasExactly" : [ {
15+
"equalTo" : "AmDIoyx_Brtnc8MngJfF8Ldb8Co6txGSCKT4anxsRG9Bk_lQW8.tNcmszBvSa01u_VKyvYmwrwKCfpwcVZWh57hliCXojDAohhoPAJsUFAbwaNGVhvvXmGpB7LPxgD4KpBrCGwHbT9gD_JZobR14JmE5ytpE7jOBE951v5SeNtPgknM_j.CfIqazLQpJzIBIgwxt8tB.CbU3M6ToIqjD8Q--"
16+
} ]
17+
}
18+
},
19+
"bodyPatterns" : [ {
20+
"equalToXml" : "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CompleteMultipartUpload xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Part><ETag>&quot;2b5c91ad399409ec9b409b66f5bb00fb&quot;</ETag><ChecksumCRC32C>OGE8Qg==</ChecksumCRC32C><PartNumber>1</PartNumber></Part><Part><ETag>&quot;267c038cc2ec32ffd0ee2512ff5da5a5&quot;</ETag><ChecksumCRC32C>RCmziw==</ChecksumCRC32C><PartNumber>2</PartNumber></Part></CompleteMultipartUpload>"
21+
} ]
22+
},
23+
"response" : {
24+
"status" : 200,
25+
"body" : "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<CompleteMultipartUploadResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Location>https://s3.us-west-2.amazonaws.com/chameleon-jcloud/conformance-tests%2Fmultipart-withChecksum</Location><Bucket>chameleon-jcloud</Bucket><Key>conformance-tests/multipart-withChecksum</Key><ETag>\"63a814bc1f352d8149b1dd9f3747e89e-2\"</ETag><ChecksumCRC32C>gDcnRA==-2</ChecksumCRC32C><ChecksumType>COMPOSITE</ChecksumType></CompleteMultipartUploadResult>",
26+
"headers" : {
27+
"Server" : "AmazonS3",
28+
"x-amz-request-id" : "DDK8YFWABC3D0TAR",
29+
"x-amz-server-side-encryption" : "AES256",
30+
"x-amz-id-2" : "8Ngcx6C3AE/MnZTFCQJD71i6vKdsI1Q3Ri8vrEUt5YCtFEMgf1nQwPzQaEfJP88zjVpJw7nKIBc=",
31+
"Date" : "Mon, 09 Mar 2026 18:31:47 GMT",
32+
"Content-Type" : "application/xml"
33+
}
34+
},
35+
"uuid" : "95d09e13-e409-431c-bb5b-578818b95b47",
36+
"persistent" : true,
37+
"insertionIndex" : 600
38+
}

0 commit comments

Comments
 (0)