|
19 | 19 | import com.google.common.io.ByteStreams; |
20 | 20 | import com.salesforce.multicloudj.blob.driver.AbstractBlobStore; |
21 | 21 | import com.salesforce.multicloudj.blob.driver.BlobIdentifier; |
22 | | -import com.salesforce.multicloudj.common.provider.Provider; |
23 | 22 | import com.salesforce.multicloudj.blob.driver.BlobInfo; |
24 | 23 | import com.salesforce.multicloudj.blob.driver.BlobMetadata; |
| 24 | +import com.salesforce.multicloudj.blob.driver.BlobStoreBuilder; |
25 | 25 | import com.salesforce.multicloudj.blob.driver.ByteArray; |
26 | 26 | import com.salesforce.multicloudj.blob.driver.CopyRequest; |
27 | 27 | import com.salesforce.multicloudj.blob.driver.CopyResponse; |
28 | | -import com.salesforce.multicloudj.blob.driver.DownloadRequest; |
29 | | -import com.salesforce.multicloudj.blob.driver.DownloadResponse; |
30 | 28 | import com.salesforce.multicloudj.blob.driver.DirectoryDownloadRequest; |
31 | 29 | import com.salesforce.multicloudj.blob.driver.DirectoryDownloadResponse; |
32 | 30 | import com.salesforce.multicloudj.blob.driver.DirectoryUploadRequest; |
33 | 31 | import com.salesforce.multicloudj.blob.driver.DirectoryUploadResponse; |
34 | | -import com.salesforce.multicloudj.blob.driver.ListBlobsPageRequest; |
35 | | -import com.salesforce.multicloudj.blob.driver.ListBlobsPageResponse; |
| 32 | +import com.salesforce.multicloudj.blob.driver.DownloadRequest; |
| 33 | +import com.salesforce.multicloudj.blob.driver.DownloadResponse; |
36 | 34 | import com.salesforce.multicloudj.blob.driver.FailedBlobDownload; |
37 | 35 | import com.salesforce.multicloudj.blob.driver.FailedBlobUpload; |
| 36 | +import com.salesforce.multicloudj.blob.driver.ListBlobsPageRequest; |
| 37 | +import com.salesforce.multicloudj.blob.driver.ListBlobsPageResponse; |
38 | 38 | import com.salesforce.multicloudj.blob.driver.ListBlobsRequest; |
39 | 39 | import com.salesforce.multicloudj.blob.driver.MultipartPart; |
40 | 40 | import com.salesforce.multicloudj.blob.driver.MultipartUpload; |
|
48 | 48 | import com.salesforce.multicloudj.common.exceptions.ResourceNotFoundException; |
49 | 49 | import com.salesforce.multicloudj.common.exceptions.SubstrateSdkException; |
50 | 50 | import com.salesforce.multicloudj.common.exceptions.UnknownException; |
51 | | -import com.salesforce.multicloudj.common.exceptions.UnSupportedOperationException; |
52 | 51 | import com.salesforce.multicloudj.common.gcp.CommonErrorCodeMapping; |
53 | 52 | import com.salesforce.multicloudj.common.gcp.GcpConstants; |
54 | 53 | import com.salesforce.multicloudj.common.gcp.GcpCredentialsProvider; |
| 54 | +import com.salesforce.multicloudj.common.provider.Provider; |
55 | 55 | import lombok.Getter; |
56 | 56 | import org.apache.http.HttpHost; |
57 | 57 | import org.apache.http.client.config.RequestConfig; |
|
65 | 65 | import java.io.IOException; |
66 | 66 | import java.io.InputStream; |
67 | 67 | import java.io.OutputStream; |
| 68 | +import java.lang.reflect.Method; |
68 | 69 | import java.net.URL; |
69 | 70 | import java.nio.channels.Channels; |
70 | 71 | import java.nio.file.Files; |
71 | 72 | import java.nio.file.Path; |
| 73 | +import java.nio.file.Paths; |
72 | 74 | import java.util.ArrayList; |
73 | 75 | import java.util.Collection; |
| 76 | +import java.util.Collections; |
74 | 77 | import java.util.HashMap; |
75 | 78 | import java.util.Iterator; |
76 | 79 | import java.util.List; |
77 | 80 | import java.util.Map; |
78 | | -import java.util.Collections; |
79 | | -import java.util.HashMap; |
80 | 81 | import java.util.UUID; |
81 | 82 | import java.util.concurrent.TimeUnit; |
82 | 83 | import java.util.stream.Collectors; |
83 | | -import java.nio.file.Paths; |
84 | 84 |
|
85 | 85 | /** |
86 | 86 | * GCP implementation of BlobStore |
|
89 | 89 | @AutoService(AbstractBlobStore.class) |
90 | 90 | public class GcpBlobStore extends AbstractBlobStore<GcpBlobStore> { |
91 | 91 |
|
92 | | - private static final String TAG_PREFIX = "gcp-tag-"; |
93 | | - |
94 | 92 | private final Storage storage; |
95 | 93 | private final GcpTransformer transformer; |
| 94 | + private static final String TAG_PREFIX = "gcp-tag-"; |
96 | 95 |
|
97 | 96 | public GcpBlobStore() { |
98 | 97 | this(new Builder(), null); |
@@ -240,7 +239,7 @@ protected void doDelete(Collection<BlobIdentifier> objects) { |
240 | 239 | List<BlobId> blobIds = objects.stream() |
241 | 240 | .map(obj -> transformer.toBlobId(bucket, obj.getKey(), obj.getVersionId())) |
242 | 241 | .collect(Collectors.toList()); |
243 | | - |
| 242 | + |
244 | 243 | storage.delete(blobIds); |
245 | 244 | } |
246 | 245 |
|
@@ -304,9 +303,9 @@ protected ListBlobsPageResponse doListPage(ListBlobsPageRequest request) { |
304 | 303 | List<BlobInfo> blobs = new ArrayList<>(); |
305 | 304 | for (Blob blob : page.getValues()) { |
306 | 305 | blobs.add(BlobInfo.builder() |
307 | | - .withKey(blob.getName()) |
308 | | - .withObjectSize(blob.getSize()) |
309 | | - .build()); |
| 306 | + .withKey(blob.getName()) |
| 307 | + .withObjectSize(blob.getSize()) |
| 308 | + .build()); |
310 | 309 | } |
311 | 310 |
|
312 | 311 | return new ListBlobsPageResponse( |
@@ -442,40 +441,30 @@ protected URL doGeneratePresignedUrl(PresignedUrlRequest request) { |
442 | 441 | httpMethod = HttpMethod.GET; |
443 | 442 | break; |
444 | 443 | } |
445 | | - List<Storage.SignUrlOption> options = new ArrayList<>(); |
446 | | - options.add(Storage.SignUrlOption.httpMethod(httpMethod)); |
447 | | - options.add(Storage.SignUrlOption.withV4Signature()); |
448 | | - |
449 | | - // Add KMS encryption header if specified |
450 | | - if (request.getKmsKeyId() != null && !request.getKmsKeyId().isEmpty()) { |
451 | | - Map<String, String> extHeaders = new HashMap<>(); |
452 | | - extHeaders.put("x-goog-encryption-kms-key-name", request.getKmsKeyId()); |
453 | | - options.add(Storage.SignUrlOption.withExtHeaders(extHeaders)); |
454 | | - } |
455 | | - |
456 | 444 | return storage.signUrl(blobInfo, |
457 | 445 | request.getDuration().toMillis(), |
458 | 446 | TimeUnit.MILLISECONDS, |
459 | | - options.toArray(new Storage.SignUrlOption[0])); |
| 447 | + Storage.SignUrlOption.httpMethod(httpMethod), |
| 448 | + Storage.SignUrlOption.withV4Signature()); |
460 | 449 | } |
461 | 450 |
|
462 | 451 | @Override |
463 | 452 | protected boolean doDoesObjectExist(String key, String versionId) { |
464 | 453 | return storage.get(transformer.toBlobId(key, versionId)) != null; |
465 | 454 | } |
466 | 455 |
|
467 | | - /** |
| 456 | + /** |
468 | 457 | * Maximum number of objects that can be deleted in a single batch operation. |
469 | 458 | * GCP supports up to 1000 objects per batch delete. |
470 | 459 | */ |
471 | 460 | private static final int MAX_OBJECTS_PER_BATCH_DELETE = 1000; |
472 | 461 |
|
| 462 | + @Override |
473 | 463 | protected DirectoryUploadResponse doUploadDirectory(DirectoryUploadRequest directoryUploadRequest) { |
474 | 464 | try { |
475 | 465 | Path sourceDir = Paths.get(directoryUploadRequest.getLocalSourceDirectory()); |
476 | 466 | List<Path> filePaths = transformer.toFilePaths(directoryUploadRequest); |
477 | 467 | List<FailedBlobUpload> failedUploads = new ArrayList<>(); |
478 | | - |
479 | 468 | // Create directory marker object if prefix is specified |
480 | 469 | if (directoryUploadRequest.getPrefix() != null && !directoryUploadRequest.getPrefix().isEmpty()) { |
481 | 470 | try { |
@@ -515,6 +504,7 @@ protected DirectoryUploadResponse doUploadDirectory(DirectoryUploadRequest direc |
515 | 504 | } |
516 | 505 | } |
517 | 506 |
|
| 507 | + @Override |
518 | 508 | protected DirectoryDownloadResponse doDownloadDirectory(DirectoryDownloadRequest req) { |
519 | 509 | try { |
520 | 510 | Path targetDir = Paths.get(req.getLocalDestinationDirectory()); |
@@ -569,6 +559,7 @@ protected DirectoryDownloadResponse doDownloadDirectory(DirectoryDownloadRequest |
569 | 559 | } |
570 | 560 | } |
571 | 561 |
|
| 562 | + @Override |
572 | 563 | protected void doDeleteDirectory(String prefix) { |
573 | 564 | try { |
574 | 565 | // List all blobs with the given prefix and delete them in batches |
@@ -644,6 +635,47 @@ public Builder withTransformerSupplier(GcpTransformerSupplier transformerSupplie |
644 | 635 | return this; |
645 | 636 | } |
646 | 637 |
|
| 638 | + /** |
| 639 | + * Copies all configuration from another BlobStoreBuilder using reflection. |
| 640 | + * This automatically handles all fields without needing manual updates when new configs are added. |
| 641 | + * @param source The source builder to copy from |
| 642 | + * @return An instance of self |
| 643 | + */ |
| 644 | + public Builder copyFrom(BlobStoreBuilder<?> source) { |
| 645 | + try { |
| 646 | + // Find all "with*" methods in this builder |
| 647 | + Method[] methods = BlobStoreBuilder.class.getDeclaredMethods(); |
| 648 | + |
| 649 | + for (Method method : methods) { |
| 650 | + String methodName = method.getName(); |
| 651 | + |
| 652 | + // Look for "with*" setter methods |
| 653 | + if (methodName.startsWith("with") && method.getParameterCount() == 1) { |
| 654 | + // Extract property name (e.g., "withBucket" -> "Bucket") |
| 655 | + String propertyName = methodName.substring(4); |
| 656 | + |
| 657 | + // Try to find corresponding getter (e.g., "getBucket") |
| 658 | + String getterName = "get" + propertyName; |
| 659 | + |
| 660 | + try { |
| 661 | + Method getter = BlobStoreBuilder.class.getMethod(getterName); |
| 662 | + Object value = getter.invoke(source); |
| 663 | + |
| 664 | + // Only copy non-null values |
| 665 | + if (value != null) { |
| 666 | + method.invoke(this, value); |
| 667 | + } |
| 668 | + } catch (NoSuchMethodException e) { |
| 669 | + // Getter doesn't exist, skip this property |
| 670 | + } |
| 671 | + } |
| 672 | + } |
| 673 | + } catch (Exception e) { |
| 674 | + throw new RuntimeException("Failed to copy builder configuration", e); |
| 675 | + } |
| 676 | + return this; |
| 677 | + } |
| 678 | + |
647 | 679 | /** |
648 | 680 | * Helper function for generating the Storage client |
649 | 681 | */ |
|
0 commit comments