Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,9 @@ protected DownloadResponse doDownload(DownloadRequest downloadRequest, ByteArray
@Override
protected DownloadResponse doDownload(DownloadRequest downloadRequest, File file) {
GetObjectRequest request = transformer.toRequest(downloadRequest);
GetObjectResponse response = s3Client.getObject(request, ResponseTransformer.toFile(file));
Path destinationPath = createDownloadDestinationPath(downloadRequest, file.toPath());
GetObjectResponse response =
s3Client.getObject(request, ResponseTransformer.toFile(destinationPath));
return transformer.toDownloadResponse(downloadRequest, response);
}

Expand All @@ -249,7 +251,9 @@ protected DownloadResponse doDownload(DownloadRequest downloadRequest, File file
@Override
protected DownloadResponse doDownload(DownloadRequest downloadRequest, Path path) {
GetObjectRequest request = transformer.toRequest(downloadRequest);
GetObjectResponse response = s3Client.getObject(request, ResponseTransformer.toFile(path));
Path destinationPath = createDownloadDestinationPath(downloadRequest, path);
GetObjectResponse response =
s3Client.getObject(request, ResponseTransformer.toFile(destinationPath));
return transformer.toDownloadResponse(downloadRequest, response);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.salesforce.multicloudj.common.retries.RetryConfig;
import com.salesforce.multicloudj.common.util.HexUtil;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
Expand Down Expand Up @@ -100,6 +101,7 @@
import software.amazon.awssdk.transfer.s3.model.CompletedDirectoryDownload;
import software.amazon.awssdk.transfer.s3.model.CompletedDirectoryUpload;
import software.amazon.awssdk.transfer.s3.model.DownloadDirectoryRequest;
import software.amazon.awssdk.transfer.s3.model.DownloadFileRequest;
import software.amazon.awssdk.transfer.s3.model.UploadDirectoryRequest;

public class AwsTransformer {
Expand Down Expand Up @@ -293,6 +295,14 @@ public GetObjectRequest toRequest(DownloadRequest request) {
return builder.build();
}

/** Builds a {@link DownloadFileRequest} for use with {@code S3TransferManager.downloadFile}. */
public DownloadFileRequest toRequest(DownloadRequest request, Path destinationPath) {
return DownloadFileRequest.builder()
.getObjectRequest(toRequest(request))
.destination(destinationPath)
.build();
}

/**
* Reading the first 500 bytes - createRangeString(0, 500) - "bytes=0-500" Reading a middle 500
* bytes - createRangeString(123, 623) - "bytes=123-623" Reading the last 500 bytes -
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,7 @@ protected CompletableFuture<DownloadResponse> doDownload(

@Override
protected CompletableFuture<DownloadResponse> doDownload(DownloadRequest request, File file) {
return client
.getObject(transformer.toRequest(request), AsyncResponseTransformer.toFile(file))
.thenApply(response -> transformer.toDownloadResponse(request, response));
return doDownload(request, file.toPath());
}

/**
Expand All @@ -185,8 +183,16 @@ protected CompletableFuture<DownloadResponse> doDownload(DownloadRequest request
*/
@Override
protected CompletableFuture<DownloadResponse> doDownload(DownloadRequest request, Path path) {
Path destinationPath = createDownloadDestinationPath(request, path);
if (request.isParallelDownload()) {
return transferManager
.downloadFile(transformer.toRequest(request, destinationPath))
.completionFuture()
.thenApply(
completed -> transformer.toDownloadResponse(request, completed.response()));
}
return client
.getObject(transformer.toRequest(request), path)
.getObject(transformer.toRequest(request), destinationPath)
.thenApply(response -> transformer.toDownloadResponse(request, response));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,27 +183,25 @@ void setup() {
.withSessionCredentials(sessionCreds)
.build();

aws =
new AwsBlobStore.Builder()
.withTransformerSupplier(transformerSupplier)
.withCredentialsOverrider(credsOverrider)
.withBucket("bucket-1")
.withRegion("us-east-2")
.withEndpoint(URI.create("https://blob.endpoint.com"))
.withProxyEndpoint(URI.create("https://proxy.endpoint.com:443"))
.withSocketTimeout(Duration.ofMinutes(1))
.withIdleConnectionTimeout(Duration.ofMinutes(5))
.withMaxConnections(100)
.build();
AwsBlobStore.Builder builder1 = new AwsBlobStore.Builder();
builder1.withTransformerSupplier(transformerSupplier);
builder1.withCredentialsOverrider(credsOverrider);
builder1.withBucket("bucket-1");
builder1.withRegion("us-east-2");
builder1.withEndpoint(URI.create("https://blob.endpoint.com"));
builder1.withProxyEndpoint(URI.create("https://proxy.endpoint.com:443"));
builder1.withSocketTimeout(Duration.ofMinutes(1));
builder1.withIdleConnectionTimeout(Duration.ofMinutes(5));
builder1.withMaxConnections(100);
aws = builder1.build();
credsOverrider =
new CredentialsOverrider.Builder(CredentialsType.ASSUME_ROLE).withRole("some-role").build();
aws =
new AwsBlobStore.Builder()
.withTransformerSupplier(transformerSupplier)
.withCredentialsOverrider(credsOverrider)
.withBucket("bucket-1")
.withRegion("us-east-2")
.build();
AwsBlobStore.Builder builder2 = new AwsBlobStore.Builder();
builder2.withTransformerSupplier(transformerSupplier);
builder2.withCredentialsOverrider(credsOverrider);
builder2.withBucket("bucket-1");
builder2.withRegion("us-east-2");
aws = builder2.build();
}

@AfterEach
Expand Down Expand Up @@ -557,6 +555,33 @@ void testDoDownloadPath() {
}
}

@Test
void testDoDownloadPath_WithCreateParentPath() throws IOException {
Instant now = Instant.now();
setupMockGetObjectResponse(now, false);

Path rootPath = Path.of("tempCreateParentRoot");
try {
Files.createDirectories(rootPath);
DownloadRequest request =
DownloadRequest.builder()
.withKey("prefix-a/prefix-b/object-1")
.withVersionId("version-1")
.withRange(10L, 110L)
.withCreateParentPath(true)
.build();
DownloadResponse response = aws.doDownload(request, rootPath);
assertEquals("prefix-a/prefix-b/object-1", response.getKey());
// Verify the intermediate parent directories were created.
assertTrue(Files.exists(rootPath.resolve("prefix-a/prefix-b")));
} finally {
Files.deleteIfExists(rootPath.resolve("prefix-a/prefix-b/object-1"));
Files.deleteIfExists(rootPath.resolve("prefix-a/prefix-b"));
Files.deleteIfExists(rootPath.resolve("prefix-a"));
Files.deleteIfExists(rootPath);
}
}

@Test
void testDoDownloadInputStream() {
Instant now = Instant.now();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -637,8 +637,7 @@ void testDoDownloadFile() throws ExecutionException, InterruptedException, IOExc
aws.doDownload(generateTestDownloadRequest(), path.toFile()).get();
ArgumentCaptor<GetObjectRequest> getObjectRequestCaptor =
ArgumentCaptor.forClass(GetObjectRequest.class);
verify(mockS3Client, times(1))
.getObject(getObjectRequestCaptor.capture(), any(AsyncResponseTransformer.class));
verify(mockS3Client, times(1)).getObject(getObjectRequestCaptor.capture(), any(Path.class));
verifyDownloadTestResults(response, getObjectRequestCaptor, now);
} finally {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"id" : "4746989d-5b8c-4c51-886c-fbbe79d07646",
"name" : "AwsBlobStoreIT_testDownload_createParentPath-DELETE-0",
"request" : {
"url" : "/chameleon-jcloud/conformance-tests/download_create_parent/nested/object_unversioned",
"method" : "DELETE"
},
"response" : {
"status" : 204,
"headers" : {
"Server" : "AmazonS3",
"x-amz-request-id" : "VXEX05CQD0KHCT36",
"x-amz-id-2" : "Mlz6UpTuS4EgJLGjOZ8OsyTVCNY1PSNKLoxzFm2Y0O9Tc48yPGdpJexIpNvjm9srWklzqMM8TpWiJRnqpTivEK7Ib/1D/8jh",
"Date" : "Wed, 08 Apr 2026 18:25:31 GMT"
}
},
"uuid" : "4746989d-5b8c-4c51-886c-fbbe79d07646",
"persistent" : true,
"scenarioName" : "scenario-1-chameleon-jcloud-conformance-tests-download_create_parent-nested-object_unversioned",
"requiredScenarioState" : "scenario-1-chameleon-jcloud-conformance-tests-download_create_parent-nested-object_unversioned-2",
"insertionIndex" : 611
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"id" : "3e9969dd-f7e1-4d98-ba06-c5445b2dc705",
"name" : "AwsBlobStoreIT_testDownload_createParentPath-DELETE-3",
"request" : {
"url" : "/chameleon-jcloud/conformance-tests/download_create_parent/nested/object_unversioned",
"method" : "DELETE"
},
"response" : {
"status" : 204,
"headers" : {
"Server" : "AmazonS3",
"x-amz-request-id" : "HCB3TTEM4VF0J5XR",
"x-amz-id-2" : "9HPuhg2cLJkvpT/Uo0HBOsNKySysE0E+7vBeJ27+axf6KHuJ5ndWDLENRcZhU+xddVmn413e3h5ziU08clcwQRcRvaQbWCgA",
"Date" : "Wed, 08 Apr 2026 18:25:30 GMT"
}
},
"uuid" : "3e9969dd-f7e1-4d98-ba06-c5445b2dc705",
"persistent" : true,
"scenarioName" : "scenario-1-chameleon-jcloud-conformance-tests-download_create_parent-nested-object_unversioned",
"requiredScenarioState" : "Started",
"newScenarioState" : "scenario-1-chameleon-jcloud-conformance-tests-download_create_parent-nested-object_unversioned-2",
"insertionIndex" : 614
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"id" : "5f1f134b-b3ee-4151-bd9d-d12f182b65c5",
"name" : "AwsBlobStoreIT_testDownload_createParentPath-GET-1",
"request" : {
"url" : "/chameleon-jcloud/conformance-tests/download_create_parent/nested/object_unversioned",
"method" : "GET"
},
"response" : {
"status" : 200,
"base64Body" : "VGhpcyBpcyB0ZXN0IGRhdGFwG71L3h7gTjsFlvY5lOuu",
"headers" : {
"Accept-Ranges" : "bytes",
"Server" : "AmazonS3",
"ETag" : "\"701bbd4bde1ee04e3b0596f63994ebae\"",
"Last-Modified" : "Wed, 08 Apr 2026 18:25:30 GMT",
"x-amz-request-id" : "VXESB9G89Y9KD5MC",
"x-amz-server-side-encryption" : "AES256",
"x-amz-id-2" : "gTtbjpHXcgEIaRPKuv6iFtF5og9wMy0hK1xWi1MDaJ7c7DJ5gt7qzNtyeovpT0cL9n3jA6XKCsDlXf7zvajREmsWo+9Qdms0",
"x-amz-transfer-encoding" : "append-md5",
"Date" : "Wed, 08 Apr 2026 18:25:31 GMT",
"Content-Type" : "application/octet-stream"
}
},
"uuid" : "5f1f134b-b3ee-4151-bd9d-d12f182b65c5",
"persistent" : true,
"scenarioName" : "scenario-2-chameleon-jcloud-conformance-tests-download_create_parent-nested-object_unversioned",
"requiredScenarioState" : "scenario-2-chameleon-jcloud-conformance-tests-download_create_parent-nested-object_unversioned-2",
"insertionIndex" : 612
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"id" : "dddd72ba-994f-42ce-a419-8b41493695e4",
"name" : "AwsBlobStoreIT_testDownload_createParentPath-GET-4",
"request" : {
"url" : "/chameleon-jcloud/conformance-tests/download_create_parent/nested/object_unversioned",
"method" : "GET"
},
"response" : {
"status" : 200,
"base64Body" : "VGhpcyBpcyB0ZXN0IGRhdGFwG71L3h7gTjsFlvY5lOuu",
"headers" : {
"Accept-Ranges" : "bytes",
"Server" : "AmazonS3",
"ETag" : "\"701bbd4bde1ee04e3b0596f63994ebae\"",
"Last-Modified" : "Wed, 08 Apr 2026 18:25:29 GMT",
"x-amz-request-id" : "MESE4HAQ08SHPET3",
"x-amz-server-side-encryption" : "AES256",
"x-amz-id-2" : "wxbH9+r/MD+ScA1mJn0JJrymn8k0DJkG7E7aTVAH8AcHBaipm7TBtq4PDO82SsToSTglTZX6FWHTr/Rdgd0CMiOQzIsN0TwG",
"x-amz-transfer-encoding" : "append-md5",
"Date" : "Wed, 08 Apr 2026 18:25:29 GMT",
"Content-Type" : "application/octet-stream"
}
},
"uuid" : "dddd72ba-994f-42ce-a419-8b41493695e4",
"persistent" : true,
"scenarioName" : "scenario-2-chameleon-jcloud-conformance-tests-download_create_parent-nested-object_unversioned",
"requiredScenarioState" : "Started",
"newScenarioState" : "scenario-2-chameleon-jcloud-conformance-tests-download_create_parent-nested-object_unversioned-2",
"insertionIndex" : 615
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"id" : "1fd4aac4-a94e-481d-92fd-4803ecf9b8e9",
"name" : "AwsBlobStoreIT_testDownload_createParentPath-PUT-2",
"request" : {
"url" : "/chameleon-jcloud/conformance-tests/download_create_parent/nested/object_unversioned",
"method" : "PUT",
"bodyPatterns" : [ {
"binaryEqualTo" : "VGhpcyBpcyB0ZXN0IGRhdGE="
} ]
},
"response" : {
"status" : 200,
"headers" : {
"Server" : "AmazonS3",
"ETag" : "\"701bbd4bde1ee04e3b0596f63994ebae\"",
"x-amz-checksum-crc64nvme" : "4G16TxxAsWw=",
"x-amz-checksum-type" : "FULL_OBJECT",
"x-amz-request-id" : "HCBB47AWN97E3P3X",
"x-amz-server-side-encryption" : "AES256",
"x-amz-id-2" : "8WD8Kbc/RSYeUT/99bgNloFHSY1UwOV9DwWJl4ALpHoMUlv7qVqqUNkzLuViEvo0Jq+7vl+Qk7n8FK/eW0mPcMSJjt6MpJAg",
"Date" : "Wed, 08 Apr 2026 18:25:30 GMT"
}
},
"uuid" : "1fd4aac4-a94e-481d-92fd-4803ecf9b8e9",
"persistent" : true,
"scenarioName" : "scenario-3-chameleon-jcloud-conformance-tests-download_create_parent-nested-object_unversioned",
"requiredScenarioState" : "scenario-3-chameleon-jcloud-conformance-tests-download_create_parent-nested-object_unversioned-2",
"insertionIndex" : 613
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"id" : "e303f571-78e5-4d9d-8e99-ba7ad93cfa67",
"name" : "AwsBlobStoreIT_testDownload_createParentPath-PUT-5",
"request" : {
"url" : "/chameleon-jcloud/conformance-tests/download_create_parent/nested/object_unversioned",
"method" : "PUT",
"bodyPatterns" : [ {
"binaryEqualTo" : "VGhpcyBpcyB0ZXN0IGRhdGE="
} ]
},
"response" : {
"status" : 200,
"headers" : {
"Server" : "AmazonS3",
"ETag" : "\"701bbd4bde1ee04e3b0596f63994ebae\"",
"x-amz-checksum-crc64nvme" : "4G16TxxAsWw=",
"x-amz-checksum-type" : "FULL_OBJECT",
"x-amz-request-id" : "MES8QY0BV3RMMP8C",
"x-amz-server-side-encryption" : "AES256",
"x-amz-id-2" : "s2JXrMu6YbChtQZ1wQnJAgwo2n8lGjVgRUzDcgUEVnMZ+sLJdlhoO93bh2/jK1gPxvrMVokyX5zSmnbnGOMgP9rnAFw7eq6A",
"Date" : "Wed, 08 Apr 2026 18:25:29 GMT"
}
},
"uuid" : "e303f571-78e5-4d9d-8e99-ba7ad93cfa67",
"persistent" : true,
"scenarioName" : "scenario-3-chameleon-jcloud-conformance-tests-download_create_parent-nested-object_unversioned",
"requiredScenarioState" : "Started",
"newScenarioState" : "scenario-3-chameleon-jcloud-conformance-tests-download_create_parent-nested-object_unversioned-2",
"insertionIndex" : 616
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@
import com.salesforce.multicloudj.blob.driver.UploadPartResponse;
import com.salesforce.multicloudj.blob.driver.UploadRequest;
import com.salesforce.multicloudj.blob.driver.UploadResponse;
import com.salesforce.multicloudj.common.exceptions.SubstrateSdkException;
import com.salesforce.multicloudj.sts.model.CredentialsOverrider;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
Expand Down Expand Up @@ -328,4 +331,25 @@ protected abstract CompletableFuture<DirectoryUploadResponse> doUploadDirectory(
DirectoryUploadRequest directoryUploadRequest);

protected abstract CompletableFuture<Void> doDeleteDirectory(String prefix);

/**
* Resolves the local download destination; when {@link DownloadRequest#isCreateParentPath()} is
* true, appends the object key and creates any missing parent directories. Subclasses may
* override to change the exception type thrown on directory-creation failure.
*/
protected Path createDownloadDestinationPath(DownloadRequest request, Path destination) {
if (!request.isCreateParentPath()) {
return destination;
}
Path resolved = destination.resolve(request.getKey()).normalize();
Path parent = resolved.getParent();
if (parent != null) {
try {
Files.createDirectories(parent);
} catch (IOException e) {
throw new SubstrateSdkException("Failed to create destination directories", e);
}
}
return resolved;
}
}
Loading
Loading