From 7bdef9a8e44e08517963e519aaed23ff19b99ade Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 16:16:52 -0500 Subject: [PATCH 01/21] chore(main): release 2.48.3-SNAPSHOT (#2924) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- gapic-google-cloud-storage-v2/pom.xml | 4 ++-- google-cloud-storage-bom/pom.xml | 16 ++++++++-------- google-cloud-storage-control/pom.xml | 4 ++-- google-cloud-storage/pom.xml | 4 ++-- grpc-google-cloud-storage-control-v2/pom.xml | 4 ++-- grpc-google-cloud-storage-v2/pom.xml | 4 ++-- pom.xml | 16 ++++++++-------- proto-google-cloud-storage-control-v2/pom.xml | 4 ++-- proto-google-cloud-storage-v2/pom.xml | 4 ++-- samples/snapshot/pom.xml | 6 +++--- storage-shared-benchmarking/pom.xml | 4 ++-- versions.txt | 14 +++++++------- 12 files changed, 42 insertions(+), 42 deletions(-) diff --git a/gapic-google-cloud-storage-v2/pom.xml b/gapic-google-cloud-storage-v2/pom.xml index cb2e9cd023..7d41ba66bf 100644 --- a/gapic-google-cloud-storage-v2/pom.xml +++ b/gapic-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc gapic-google-cloud-storage-v2 - 2.48.2 + 2.48.3-SNAPSHOT gapic-google-cloud-storage-v2 GRPC library for gapic-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.48.2 + 2.48.3-SNAPSHOT diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml index 627a7c148f..b48582d648 100644 --- a/google-cloud-storage-bom/pom.xml +++ b/google-cloud-storage-bom/pom.xml @@ -19,7 +19,7 @@ 4.0.0 com.google.cloud google-cloud-storage-bom - 2.48.2 + 2.48.3-SNAPSHOT pom com.google.cloud @@ -69,37 +69,37 @@ com.google.cloud google-cloud-storage - 2.48.2 + 2.48.3-SNAPSHOT com.google.api.grpc gapic-google-cloud-storage-v2 - 2.48.2 + 2.48.3-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-v2 - 2.48.2 + 2.48.3-SNAPSHOT com.google.api.grpc proto-google-cloud-storage-v2 - 2.48.2 + 2.48.3-SNAPSHOT com.google.cloud google-cloud-storage-control - 2.48.2 + 2.48.3-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.48.2 + 2.48.3-SNAPSHOT com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.48.2 + 2.48.3-SNAPSHOT diff --git a/google-cloud-storage-control/pom.xml b/google-cloud-storage-control/pom.xml index 2b8e88742d..d59b2ed440 100644 --- a/google-cloud-storage-control/pom.xml +++ b/google-cloud-storage-control/pom.xml @@ -5,13 +5,13 @@ 4.0.0 com.google.cloud google-cloud-storage-control - 2.48.2 + 2.48.3-SNAPSHOT google-cloud-storage-control GRPC library for google-cloud-storage-control com.google.cloud google-cloud-storage-parent - 2.48.2 + 2.48.3-SNAPSHOT diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 3aea8e1387..94384ff42b 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -2,7 +2,7 @@ 4.0.0 google-cloud-storage - 2.48.2 + 2.48.3-SNAPSHOT jar Google Cloud Storage https://github.com/googleapis/java-storage @@ -12,7 +12,7 @@ com.google.cloud google-cloud-storage-parent - 2.48.2 + 2.48.3-SNAPSHOT google-cloud-storage diff --git a/grpc-google-cloud-storage-control-v2/pom.xml b/grpc-google-cloud-storage-control-v2/pom.xml index 0b87ed7234..151bef0d03 100644 --- a/grpc-google-cloud-storage-control-v2/pom.xml +++ b/grpc-google-cloud-storage-control-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.48.2 + 2.48.3-SNAPSHOT grpc-google-cloud-storage-control-v2 GRPC library for google-cloud-storage com.google.cloud google-cloud-storage-parent - 2.48.2 + 2.48.3-SNAPSHOT diff --git a/grpc-google-cloud-storage-v2/pom.xml b/grpc-google-cloud-storage-v2/pom.xml index 3d9c41d8e2..28447a6148 100644 --- a/grpc-google-cloud-storage-v2/pom.xml +++ b/grpc-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-storage-v2 - 2.48.2 + 2.48.3-SNAPSHOT grpc-google-cloud-storage-v2 GRPC library for grpc-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.48.2 + 2.48.3-SNAPSHOT diff --git a/pom.xml b/pom.xml index c94d752660..20cfa8e4af 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-storage-parent pom - 2.48.2 + 2.48.3-SNAPSHOT Storage Parent https://github.com/googleapis/java-storage @@ -123,7 +123,7 @@ com.google.cloud google-cloud-storage - 2.48.2 + 2.48.3-SNAPSHOT com.google.apis @@ -145,32 +145,32 @@ com.google.api.grpc proto-google-cloud-storage-v2 - 2.48.2 + 2.48.3-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-v2 - 2.48.2 + 2.48.3-SNAPSHOT com.google.api.grpc gapic-google-cloud-storage-v2 - 2.48.2 + 2.48.3-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.48.2 + 2.48.3-SNAPSHOT com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.48.2 + 2.48.3-SNAPSHOT com.google.cloud google-cloud-storage-control - 2.48.2 + 2.48.3-SNAPSHOT com.google.cloud diff --git a/proto-google-cloud-storage-control-v2/pom.xml b/proto-google-cloud-storage-control-v2/pom.xml index ef015556b7..b44e969e7c 100644 --- a/proto-google-cloud-storage-control-v2/pom.xml +++ b/proto-google-cloud-storage-control-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.48.2 + 2.48.3-SNAPSHOT proto-google-cloud-storage-control-v2 Proto library for proto-google-cloud-storage-control-v2 com.google.cloud google-cloud-storage-parent - 2.48.2 + 2.48.3-SNAPSHOT diff --git a/proto-google-cloud-storage-v2/pom.xml b/proto-google-cloud-storage-v2/pom.xml index 294f37ad6c..cf1bd588ba 100644 --- a/proto-google-cloud-storage-v2/pom.xml +++ b/proto-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-storage-v2 - 2.48.2 + 2.48.3-SNAPSHOT proto-google-cloud-storage-v2 PROTO library for proto-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.48.2 + 2.48.3-SNAPSHOT diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 0c967682e4..72f14d0b45 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,12 +28,12 @@ com.google.cloud google-cloud-storage - 2.48.2 + 2.48.3-SNAPSHOT com.google.cloud google-cloud-storage-control - 2.48.2 + 2.48.3-SNAPSHOT compile @@ -64,7 +64,7 @@ com.google.cloud google-cloud-storage - 2.48.2 + 2.48.3-SNAPSHOT tests test diff --git a/storage-shared-benchmarking/pom.xml b/storage-shared-benchmarking/pom.xml index 5f82e69460..5f88686807 100644 --- a/storage-shared-benchmarking/pom.xml +++ b/storage-shared-benchmarking/pom.xml @@ -10,7 +10,7 @@ com.google.cloud google-cloud-storage-parent - 2.48.2 + 2.48.3-SNAPSHOT @@ -31,7 +31,7 @@ com.google.cloud google-cloud-storage - 2.48.2 + 2.48.3-SNAPSHOT tests diff --git a/versions.txt b/versions.txt index 30f29e688c..d23fc17700 100644 --- a/versions.txt +++ b/versions.txt @@ -1,10 +1,10 @@ # Format: # module:released-version:current-version -google-cloud-storage:2.48.2:2.48.2 -gapic-google-cloud-storage-v2:2.48.2:2.48.2 -grpc-google-cloud-storage-v2:2.48.2:2.48.2 -proto-google-cloud-storage-v2:2.48.2:2.48.2 -google-cloud-storage-control:2.48.2:2.48.2 -proto-google-cloud-storage-control-v2:2.48.2:2.48.2 -grpc-google-cloud-storage-control-v2:2.48.2:2.48.2 +google-cloud-storage:2.48.2:2.48.3-SNAPSHOT +gapic-google-cloud-storage-v2:2.48.2:2.48.3-SNAPSHOT +grpc-google-cloud-storage-v2:2.48.2:2.48.3-SNAPSHOT +proto-google-cloud-storage-v2:2.48.2:2.48.3-SNAPSHOT +google-cloud-storage-control:2.48.2:2.48.3-SNAPSHOT +proto-google-cloud-storage-control-v2:2.48.2:2.48.3-SNAPSHOT +grpc-google-cloud-storage-control-v2:2.48.2:2.48.3-SNAPSHOT From cc037848be7d21cb827c97d7f71618f1bfae941d Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Thu, 13 Feb 2025 14:03:30 -0500 Subject: [PATCH 02/21] fix: update grpc based Storage to defer project id validation (#2930) Project id is only needed as a default for some operations, don't prevent constructing an instance of GrpcStorageImpl if project id is unresolved. --- .../google/cloud/storage/GrpcStorageImpl.java | 7 ++++-- .../storage/it/ITStorageOptionsTest.java | 25 ++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java index 69781270b0..f822130823 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java @@ -69,6 +69,7 @@ import com.google.cloud.storage.UnifiedOpts.ProjectId; import com.google.cloud.storage.UnifiedOpts.UserProject; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Streams; @@ -130,6 +131,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.function.UnaryOperator; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -167,7 +169,7 @@ final class GrpcStorageImpl extends BaseService // workaround for https://github.com/googleapis/java-storage/issues/1736 private final Opts defaultOpts; - @Deprecated private final ProjectId defaultProjectId; + @Deprecated private final Supplier defaultProjectId; GrpcStorageImpl( GrpcStorageOptions options, @@ -183,7 +185,7 @@ final class GrpcStorageImpl extends BaseService this.codecs = Conversions.grpc(); this.retryAlgorithmManager = options.getRetryAlgorithmManager(); this.syntaxDecoders = new SyntaxDecoders(); - this.defaultProjectId = UnifiedOpts.projectId(options.getProjectId()); + this.defaultProjectId = Suppliers.memoize(() -> UnifiedOpts.projectId(options.getProjectId())); } @Override @@ -443,6 +445,7 @@ public Page list(BucketListOption... options) { opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); ListBucketsRequest request = defaultProjectId + .get() .listBuckets() .andThen(opts.listBucketsRequest()) .apply(ListBucketsRequest.newBuilder()) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageOptionsTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageOptionsTest.java index 86bf0a4b82..0d8114a7fb 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageOptionsTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageOptionsTest.java @@ -33,7 +33,6 @@ import com.google.cloud.storage.it.runner.annotations.Parameterized.ParametersProvider; import com.google.cloud.storage.it.runner.annotations.SingleBackend; import com.google.common.collect.ImmutableList; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -73,12 +72,32 @@ public void clientShouldConstructCleanly_grpc() throws Exception { } @Test - @Ignore("waiting on conformation from the backend team if this should even be possible") public void clientShouldConstructCleanly_directPath() throws Exception { assumeTrue( "Unable to determine environment can access directPath", TestUtils.isOnComputeEngine()); StorageOptions options = - StorageOptions.grpc().setCredentials(credentials).setAttemptDirectPath(true).build(); + StorageOptions.grpc() + .setCredentials(credentials) + .setAttemptDirectPath(true) + .setEnableGrpcClientMetrics(false) + .build(); + doTest(options); + } + + @Test + public void lackOfProjectIdDoesNotPreventConstruction_http() throws Exception { + StorageOptions options = StorageOptions.http().setCredentials(credentials).build(); + doTest(options); + } + + @Test + public void lackOfProjectIdDoesNotPreventConstruction_grpc() throws Exception { + StorageOptions options = + StorageOptions.grpc() + .setCredentials(credentials) + .setAttemptDirectPath(false) + .setEnableGrpcClientMetrics(false) + .build(); doTest(options); } From c461546bfd016b21eb99f8e25604ee3fd001c9c6 Mon Sep 17 00:00:00 2001 From: Denis DelGrosso <85250797+ddelgrosso1@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:07:10 -0500 Subject: [PATCH 03/21] docs: add note about HNS support to moveBlob (#2929) --- .../src/main/java/com/google/cloud/storage/Storage.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 2fa0960281..8d6f479eb9 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -5036,6 +5036,8 @@ default BlobWriteSession blobWriteSession(BlobInfo blobInfo, BlobWriteOption... * {@link Storage#delete(BlobId)}, however without the ability to change metadata fields for the * target object. * + *

This feature is currently only supported for HNS (Hierarchical Namespace) buckets. + * * @since 2.48.0 */ @TransportCompatibility({Transport.HTTP, Transport.GRPC}) From a5d6c7e3e15e233805a1a29912fcd60a916569ec Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 13 Feb 2025 21:09:02 +0100 Subject: [PATCH 04/21] test(deps): update cross product test dependencies (#2928) --- google-cloud-storage/pom.xml | 2 +- pom.xml | 2 +- samples/install-without-bom/pom.xml | 2 +- samples/snapshot/pom.xml | 2 +- samples/snippets/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 94384ff42b..6f7d51b486 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -16,7 +16,7 @@ google-cloud-storage - 1.118.1 + 1.119.0 diff --git a/pom.xml b/pom.xml index 20cfa8e4af..cf4e8e8bf2 100644 --- a/pom.xml +++ b/pom.xml @@ -133,7 +133,7 @@ com.google.cloud google-cloud-pubsub - 1.136.1 + 1.137.0 test diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index a577ad518d..6d9a6de12d 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -66,7 +66,7 @@ com.google.cloud google-cloud-pubsub - 1.136.1 + 1.137.0 test diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 72f14d0b45..f97ded7fa1 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -58,7 +58,7 @@ com.google.cloud google-cloud-pubsub - 1.136.1 + 1.137.0 test diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 121deb87e6..ce4cea38c3 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -76,7 +76,7 @@ com.google.cloud google-cloud-pubsub - 1.136.1 + 1.137.0 test From 4fde024b74d069aa62b6fb011c3f95c7d5b355e9 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 13 Feb 2025 21:40:01 +0100 Subject: [PATCH 05/21] chore(deps): update storage release dependencies to v2.48.2 (#2926) --- samples/install-without-bom/pom.xml | 6 +++--- samples/snippets/pom.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 6d9a6de12d..c81c72d9ca 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -30,12 +30,12 @@ com.google.cloud google-cloud-storage - 2.48.1 + 2.48.2 com.google.cloud google-cloud-storage-control - 2.48.1 + 2.48.2 @@ -72,7 +72,7 @@ com.google.cloud google-cloud-storage - 2.48.1 + 2.48.2 tests test diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index ce4cea38c3..7a51759598 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -93,7 +93,7 @@ com.google.cloud google-cloud-storage - 2.48.1 + 2.48.2 tests test From 9946d6bdc7ec8398bf1bd1df63f272df1351539e Mon Sep 17 00:00:00 2001 From: cloud-java-bot <122572305+cloud-java-bot@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:06:40 -0500 Subject: [PATCH 06/21] chore: Update generation configuration at Fri Feb 14 02:22:08 UTC 2025 (#2934) * chore: Update generation configuration at Fri Feb 14 02:22:08 UTC 2025 * chore: generate libraries at Fri Feb 14 02:22:48 UTC 2025 --- README.md | 4 ++-- generation_config.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bdfcc95617..6ca40a1a3c 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,12 @@ If you are using Maven without the BOM, add this to your dependencies: com.google.cloud google-cloud-storage - 2.48.1 + 2.48.2 com.google.cloud google-cloud-storage-control - 2.48.1 + 2.48.2 ``` diff --git a/generation_config.yaml b/generation_config.yaml index 1d62fcf9fa..0268d85ff6 100644 --- a/generation_config.yaml +++ b/generation_config.yaml @@ -1,5 +1,5 @@ -gapic_generator_version: 2.52.0 -googleapis_commitish: a1c746a0304b9d0d913ab013cb248ce605a6871b +gapic_generator_version: 2.53.0 +googleapis_commitish: a003cab30e2a263e16e9252256041f8934f40e2c libraries_bom_version: 26.54.0 libraries: - api_shortname: storage From e8ba858ec81d74fa15a00a13e94d0944148b0a74 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 14 Feb 2025 21:07:23 +0100 Subject: [PATCH 07/21] test(deps): update cross product test dependencies (#2933) --- google-cloud-storage/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 6f7d51b486..ec307a7ddb 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -239,14 +239,14 @@ com.google.api.grpc proto-google-cloud-kms-v1 - 0.151.0 + 0.152.0 test com.google.cloud google-cloud-kms - 2.60.0 + 2.61.0 test From 86e9ae80772aa202d0b6563b8dd37722d8b5e0e0 Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Tue, 18 Feb 2025 17:35:25 -0500 Subject: [PATCH 08/21] feat(transfer-manager): add ParallelUploadConfig.Builder#setUploadBlobInfoFactory (#2936) When uploading a file from the files system, there are scenarios when a job would need to customize the actual BlobInfo used to upload to GCS. Add the new UploadBlobInfoFactory which allows a user to produce their own BlobInfo instance given the bucketName and fileName. When producing the BlobInfo the application can also customize other metadata fields. A few convenience adapter methods are available in UploadBlobInfoFactory to simplify common operations. Fixes #2638 --- .../BucketNameMismatchException.java | 27 +++ .../transfermanager/ParallelUploadConfig.java | 158 ++++++++++++++++-- .../transfermanager/TransferManagerImpl.java | 13 +- .../transfermanager/TransferManagerUtils.java | 8 - .../storage/it/ITTransferManagerTest.java | 108 ++++++++++++ .../transfermanager/TransferManagerTest.java | 65 +++++++ 6 files changed, 357 insertions(+), 22 deletions(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/BucketNameMismatchException.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/transfermanager/TransferManagerTest.java diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/BucketNameMismatchException.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/BucketNameMismatchException.java new file mode 100644 index 0000000000..0391722989 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/BucketNameMismatchException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage.transfermanager; + +public final class BucketNameMismatchException extends RuntimeException { + + public BucketNameMismatchException(String actual, String expected) { + super( + String.format( + "Bucket name in produced BlobInfo did not match bucket name from config. (%s != %s)", + actual, expected)); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/ParallelUploadConfig.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/ParallelUploadConfig.java index 615d85427b..1497210f86 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/ParallelUploadConfig.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/ParallelUploadConfig.java @@ -18,11 +18,13 @@ import static com.google.common.base.Preconditions.checkNotNull; +import com.google.cloud.storage.BlobInfo; import com.google.cloud.storage.Storage.BlobWriteOption; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import java.util.List; import java.util.Objects; +import java.util.function.Function; import org.checkerframework.checker.nullness.qual.NonNull; /** @@ -33,19 +35,19 @@ public final class ParallelUploadConfig { private final boolean skipIfExists; - @NonNull private final String prefix; @NonNull private final String bucketName; + @NonNull private final UploadBlobInfoFactory uploadBlobInfoFactory; @NonNull private final List writeOptsPerRequest; private ParallelUploadConfig( boolean skipIfExists, - @NonNull String prefix, @NonNull String bucketName, + @NonNull UploadBlobInfoFactory uploadBlobInfoFactory, @NonNull List writeOptsPerRequest) { this.skipIfExists = skipIfExists; - this.prefix = prefix; this.bucketName = bucketName; + this.uploadBlobInfoFactory = uploadBlobInfoFactory; this.writeOptsPerRequest = applySkipIfExists(skipIfExists, writeOptsPerRequest); } @@ -63,9 +65,26 @@ public boolean isSkipIfExists() { * A common prefix that will be applied to all object paths in the destination bucket * * @see Builder#setPrefix(String) + * @see Builder#setUploadBlobInfoFactory(UploadBlobInfoFactory) + * @see UploadBlobInfoFactory#prefixObjectNames(String) */ public @NonNull String getPrefix() { - return prefix; + if (uploadBlobInfoFactory instanceof PrefixObjectNames) { + PrefixObjectNames prefixObjectNames = (PrefixObjectNames) uploadBlobInfoFactory; + return prefixObjectNames.prefix; + } + return ""; + } + + /** + * The {@link UploadBlobInfoFactory} which will be used to produce a {@link BlobInfo}s based on a + * provided bucket name and file name. + * + * @see Builder#setUploadBlobInfoFactory(UploadBlobInfoFactory) + * @since 2.49.0 + */ + public @NonNull UploadBlobInfoFactory getUploadBlobInfoFactory() { + return uploadBlobInfoFactory; } /** @@ -96,22 +115,22 @@ public boolean equals(Object o) { } ParallelUploadConfig that = (ParallelUploadConfig) o; return skipIfExists == that.skipIfExists - && prefix.equals(that.prefix) && bucketName.equals(that.bucketName) + && uploadBlobInfoFactory.equals(that.uploadBlobInfoFactory) && writeOptsPerRequest.equals(that.writeOptsPerRequest); } @Override public int hashCode() { - return Objects.hash(skipIfExists, prefix, bucketName, writeOptsPerRequest); + return Objects.hash(skipIfExists, bucketName, uploadBlobInfoFactory, writeOptsPerRequest); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("skipIfExists", skipIfExists) - .add("prefix", prefix) .add("bucketName", bucketName) + .add("uploadBlobInfoFactory", uploadBlobInfoFactory) .add("writeOptsPerRequest", writeOptsPerRequest) .toString(); } @@ -137,13 +156,13 @@ private static List applySkipIfExists( public static final class Builder { private boolean skipIfExists; - private @NonNull String prefix; private @NonNull String bucketName; + private @NonNull UploadBlobInfoFactory uploadBlobInfoFactory; private @NonNull List writeOptsPerRequest; private Builder() { - this.prefix = ""; this.bucketName = ""; + this.uploadBlobInfoFactory = UploadBlobInfoFactory.defaultInstance(); this.writeOptsPerRequest = ImmutableList.of(); } @@ -162,11 +181,37 @@ public Builder setSkipIfExists(boolean skipIfExists) { /** * Sets a common prefix that will be applied to all object paths in the destination bucket. * + *

NOTE: this method and {@link #setUploadBlobInfoFactory(UploadBlobInfoFactory)} are + * mutually exclusive, and last invocation "wins". + * * @return the builder instance with the value for prefix modified. * @see ParallelUploadConfig#getPrefix() + * @see ParallelUploadConfig.Builder#setUploadBlobInfoFactory(UploadBlobInfoFactory) + * @see UploadBlobInfoFactory#prefixObjectNames(String) */ public Builder setPrefix(@NonNull String prefix) { - this.prefix = prefix; + this.uploadBlobInfoFactory = UploadBlobInfoFactory.prefixObjectNames(prefix); + return this; + } + + /** + * Sets a {@link UploadBlobInfoFactory} which can be used to produce a custom BlobInfo based on + * a provided bucket name and file name. + * + *

The bucket name in the returned BlobInfo MUST be equal to the value provided to {@link + * #setBucketName(String)}, if not that upload will fail with a {@link + * TransferStatus#FAILED_TO_START} and a {@link BucketNameMismatchException}. + * + *

NOTE: this method and {@link #setPrefix(String)} are mutually exclusive, and last + * invocation "wins". + * + * @return the builder instance with the value for uploadBlobInfoFactory modified. + * @see ParallelUploadConfig#getPrefix() + * @see ParallelUploadConfig#getUploadBlobInfoFactory() + * @since 2.49.0 + */ + public Builder setUploadBlobInfoFactory(@NonNull UploadBlobInfoFactory uploadBlobInfoFactory) { + this.uploadBlobInfoFactory = uploadBlobInfoFactory; return this; } @@ -199,10 +244,99 @@ public Builder setWriteOptsPerRequest(@NonNull List writeOptsPe * @return {@link ParallelUploadConfig} */ public ParallelUploadConfig build() { - checkNotNull(prefix); checkNotNull(bucketName); + checkNotNull(uploadBlobInfoFactory); checkNotNull(writeOptsPerRequest); - return new ParallelUploadConfig(skipIfExists, prefix, bucketName, writeOptsPerRequest); + return new ParallelUploadConfig( + skipIfExists, bucketName, uploadBlobInfoFactory, writeOptsPerRequest); + } + } + + public interface UploadBlobInfoFactory { + + /** + * Method to produce a {@link BlobInfo} to be used for the upload to Cloud Storage. + * + *

The bucket name in the returned BlobInfo MUST be equal to the value provided to the {@link + * ParallelUploadConfig.Builder#setBucketName(String)}, if not that upload will fail with a + * {@link TransferStatus#FAILED_TO_START} and a {@link BucketNameMismatchException}. + * + * @param bucketName The name of the bucket to be uploaded to. The value provided here will be + * the value from {@link ParallelUploadConfig#getBucketName()}. + * @param fileName The String representation of the absolute path of the file to be uploaded + * @return The instance of {@link BlobInfo} that should be used to upload the file to Cloud + * Storage. + */ + BlobInfo apply(String bucketName, String fileName); + + /** + * Adapter factory to provide the same semantics as if using {@link Builder#setPrefix(String)} + */ + static UploadBlobInfoFactory prefixObjectNames(String prefix) { + return new PrefixObjectNames(prefix); + } + + /** The default instance which applies not modification to the provided {@code fileName} */ + static UploadBlobInfoFactory defaultInstance() { + return DefaultUploadBlobInfoFactory.INSTANCE; + } + + /** + * Convenience method to "lift" a {@link Function} that transforms the file name to an {@link + * UploadBlobInfoFactory} + */ + static UploadBlobInfoFactory transformFileName(Function fileNameTransformer) { + return (b, f) -> BlobInfo.newBuilder(b, fileNameTransformer.apply(f)).build(); + } + } + + private static final class DefaultUploadBlobInfoFactory implements UploadBlobInfoFactory { + private static final DefaultUploadBlobInfoFactory INSTANCE = new DefaultUploadBlobInfoFactory(); + + private DefaultUploadBlobInfoFactory() {} + + @Override + public BlobInfo apply(String bucketName, String fileName) { + return BlobInfo.newBuilder(bucketName, fileName).build(); + } + } + + private static final class PrefixObjectNames implements UploadBlobInfoFactory { + private final String prefix; + + private PrefixObjectNames(String prefix) { + this.prefix = prefix; + } + + @Override + public BlobInfo apply(String bucketName, String fileName) { + String separator = ""; + if (!fileName.startsWith("/")) { + separator = "/"; + } + return BlobInfo.newBuilder(bucketName, prefix + separator + fileName).build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PrefixObjectNames)) { + return false; + } + PrefixObjectNames that = (PrefixObjectNames) o; + return Objects.equals(prefix, that.prefix); + } + + @Override + public int hashCode() { + return Objects.hashCode(prefix); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("prefix", prefix).toString(); } } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/TransferManagerImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/TransferManagerImpl.java index 57e56ea201..13f7d400a2 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/TransferManagerImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/TransferManagerImpl.java @@ -110,8 +110,17 @@ public void close() throws Exception { List> uploadTasks = new ArrayList<>(); for (Path file : files) { if (Files.isDirectory(file)) throw new IllegalStateException("Directories are not supported"); - String blobName = TransferManagerUtils.createBlobName(config, file); - BlobInfo blobInfo = BlobInfo.newBuilder(config.getBucketName(), blobName).build(); + String bucketName = config.getBucketName(); + BlobInfo blobInfo = + config.getUploadBlobInfoFactory().apply(bucketName, file.toAbsolutePath().toString()); + if (!blobInfo.getBucket().equals(bucketName)) { + uploadTasks.add( + ApiFutures.immediateFuture( + UploadResult.newBuilder(blobInfo, TransferStatus.FAILED_TO_START) + .setException(new BucketNameMismatchException(blobInfo.getBucket(), bucketName)) + .build())); + continue; + } if (transferManagerConfig.isAllowParallelCompositeUpload() && qos.parallelCompositeUpload(Files.size(file))) { ParallelCompositeUploadCallable callable = diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/TransferManagerUtils.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/TransferManagerUtils.java index 607dd3148b..a884aa7067 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/TransferManagerUtils.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/transfermanager/TransferManagerUtils.java @@ -26,14 +26,6 @@ final class TransferManagerUtils { private TransferManagerUtils() {} - static String createBlobName(ParallelUploadConfig config, Path file) { - if (config.getPrefix().isEmpty()) { - return file.toString(); - } else { - return config.getPrefix().concat(file.toString()); - } - } - static Path createDestPath(ParallelDownloadConfig config, BlobInfo originalBlob) { Path newPath = config diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITTransferManagerTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITTransferManagerTest.java index 504de7164e..bd116cf172 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITTransferManagerTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITTransferManagerTest.java @@ -36,6 +36,7 @@ import com.google.cloud.storage.it.runner.annotations.CrossRun; import com.google.cloud.storage.it.runner.annotations.Inject; import com.google.cloud.storage.it.runner.registry.Generator; +import com.google.cloud.storage.transfermanager.BucketNameMismatchException; import com.google.cloud.storage.transfermanager.DownloadJob; import com.google.cloud.storage.transfermanager.DownloadResult; import com.google.cloud.storage.transfermanager.ParallelDownloadConfig; @@ -47,6 +48,7 @@ import com.google.cloud.storage.transfermanager.UploadJob; import com.google.cloud.storage.transfermanager.UploadResult; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.Files; @@ -57,7 +59,11 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -525,6 +531,108 @@ public void downloadBlobsOneFailure() throws Exception { } } + @Test + public void uploadChangePrefix() throws Exception { + try (TmpFile tmpFile1 = DataGenerator.base64Characters().tempFile(baseDir, 373); + TmpFile tmpFile2 = + DataGenerator.base64Characters().tempFile(baseDir, 2 * 1024 * 1024 + 13); + TransferManager tm = + TransferManagerConfig.newBuilder() + .setMaxWorkers(1) + .setPerWorkerBufferSize(4 * 1024 * 1024) + .setAllowDivideAndConquerDownload(false) + .setAllowParallelCompositeUpload(false) + .setStorageOptions(storage.getOptions()) + .build() + .getService()) { + + String prefix = "asdfasdf"; + ImmutableMap<@NonNull String, @Nullable String> metadata = ImmutableMap.of("k", "v"); + String contentType = "text/plain;charset=utf-8"; + ParallelUploadConfig uploadConfig = + ParallelUploadConfig.newBuilder() + .setBucketName(bucket.getName()) + .setSkipIfExists(false) + .setUploadBlobInfoFactory( + (b, f) -> + BlobInfo.newBuilder( + b, prefix + f.replace(baseDir.toAbsolutePath().toString(), "")) + .setContentType(contentType) + .setMetadata(metadata) + .build()) + .setWriteOptsPerRequest(ImmutableList.of(BlobWriteOption.doesNotExist())) + .build(); + + ImmutableList files = ImmutableList.of(tmpFile1.getPath(), tmpFile2.getPath()); + UploadJob uploadJob = tm.uploadFiles(files, uploadConfig); + List uploadResults = uploadJob.getUploadResults(); + + List expected = + files.stream() + .map(p -> p.getFileName().toString()) + .map(s -> prefix + "/" + s) + .collect(Collectors.toList()); + + List actualGsUtilUris = + uploadResults.stream() + .map(UploadResult::getUploadedBlob) + .map(BlobInfo::getName) + .collect(Collectors.toList()); + assertThat(actualGsUtilUris).containsExactlyElementsIn(expected); + + List> actualMetadatas = + uploadResults.stream() + .map(UploadResult::getUploadedBlob) + .map(BlobInfo::getMetadata) + .collect(Collectors.toList()); + + assertThat(actualMetadatas).isEqualTo(ImmutableList.of(metadata, metadata)); + + List actualContentTypes = + uploadResults.stream() + .map(UploadResult::getUploadedBlob) + .map(BlobInfo::getContentType) + .collect(Collectors.toList()); + + assertThat(actualContentTypes).isEqualTo(ImmutableList.of(contentType, contentType)); + } + } + + @Test + public void bucketNameFromUploadBlobInfoFactoryMustMatchConfig() throws Exception { + try (TmpFile tmpFile1 = DataGenerator.base64Characters().tempFile(baseDir, 373); + TransferManager tm = + TransferManagerConfig.newBuilder() + .setMaxWorkers(1) + .setPerWorkerBufferSize(4 * 1024 * 1024) + .setAllowDivideAndConquerDownload(false) + .setAllowParallelCompositeUpload(false) + .setStorageOptions(storage.getOptions()) + .build() + .getService()) { + + ParallelUploadConfig uploadConfig = + ParallelUploadConfig.newBuilder() + .setBucketName(bucket.getName()) + .setSkipIfExists(false) + .setUploadBlobInfoFactory((b, f) -> BlobInfo.newBuilder(b + "x", f).build()) + .setWriteOptsPerRequest(ImmutableList.of(BlobWriteOption.doesNotExist())) + .build(); + + ImmutableList files = ImmutableList.of(tmpFile1.getPath()); + UploadJob uploadJob = tm.uploadFiles(files, uploadConfig); + List uploadResults = uploadJob.getUploadResults(); + + Optional failedToStart = + uploadResults.stream() + .filter(r -> r.getStatus() == TransferStatus.FAILED_TO_START) + .findAny(); + assertThat(failedToStart).isPresent(); + UploadResult result = failedToStart.get(); + assertThat(result.getException()).isInstanceOf(BucketNameMismatchException.class); + } + } + private void cleanUpFiles(List results) throws IOException { // Cleanup downloaded blobs and the parent directory for (DownloadResult res : results) { diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/transfermanager/TransferManagerTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/transfermanager/TransferManagerTest.java new file mode 100644 index 0000000000..cc489ca07a --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/transfermanager/TransferManagerTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage.transfermanager; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.transfermanager.ParallelUploadConfig.UploadBlobInfoFactory; +import java.util.function.Function; +import org.junit.Test; + +public final class TransferManagerTest { + + @Test + public void uploadBlobInfoFactory_prefixObjectNames_leadingSlash() { + UploadBlobInfoFactory factory = UploadBlobInfoFactory.prefixObjectNames("asdf"); + + BlobInfo info = factory.apply("bucket", "/f/i/l/e/n/a/m/e.txt"); + assertThat(info.getBucket()).isEqualTo("bucket"); + assertThat(info.getName()).isEqualTo("asdf/f/i/l/e/n/a/m/e.txt"); + } + + @Test + public void uploadBlobInfoFactory_prefixObjectNames() { + UploadBlobInfoFactory factory = UploadBlobInfoFactory.prefixObjectNames("asdf"); + + BlobInfo info = factory.apply("bucket", "n/a/m/e.txt"); + assertThat(info.getBucket()).isEqualTo("bucket"); + assertThat(info.getName()).isEqualTo("asdf/n/a/m/e.txt"); + } + + @Test + public void uploadBlobInfoFactory_transformFileName() { + UploadBlobInfoFactory factory = + UploadBlobInfoFactory.transformFileName( + Function.identity().andThen(s -> s + "|").compose(s -> "|" + s)); + + BlobInfo info = factory.apply("bucket", "/e.txt"); + assertThat(info.getBucket()).isEqualTo("bucket"); + assertThat(info.getName()).isEqualTo("|/e.txt|"); + } + + @Test + public void uploadBlobInfoFactory_default_doesNotModify() { + UploadBlobInfoFactory factory = UploadBlobInfoFactory.defaultInstance(); + + BlobInfo info = factory.apply("bucket", "/e.txt"); + assertThat(info.getBucket()).isEqualTo("bucket"); + assertThat(info.getName()).isEqualTo("/e.txt"); + } +} From 43553dedce33093e751143fadb372024d975706c Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Tue, 18 Feb 2025 18:56:46 -0500 Subject: [PATCH 09/21] fix: update kms key handling when opening a resumable upload to clear the value in the json to be null rather than empty string (#2939) Related to b/395940239 --- .../java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java index 3aa17cc2dc..fc8553e7e3 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java @@ -1082,7 +1082,7 @@ public String open(StorageObject object, Map options) { try { String kmsKeyName = object.getKmsKeyName(); if (kmsKeyName != null && kmsKeyName.contains("cryptoKeyVersions")) { - object.setKmsKeyName(""); + object.setKmsKeyName(Data.nullOf(String.class)); } Insert req = storage From bb1befca845c48e890284f21fbc174cdec354a44 Mon Sep 17 00:00:00 2001 From: cloud-java-bot <122572305+cloud-java-bot@users.noreply.github.com> Date: Mon, 24 Feb 2025 12:40:14 -0500 Subject: [PATCH 10/21] chore: Update generation configuration at Mon Feb 24 02:25:11 UTC 2025 (#2947) --- README.md | 2 +- generation_config.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6ca40a1a3c..e44bc8f65c 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ If you are using Maven without the BOM, add this to your dependencies: If you are using Gradle 5.x or later, add this to your dependencies: ```Groovy -implementation platform('com.google.cloud:libraries-bom:26.54.0') +implementation platform('com.google.cloud:libraries-bom:26.55.0') implementation 'com.google.cloud:google-cloud-storage' ``` diff --git a/generation_config.yaml b/generation_config.yaml index 0268d85ff6..c288bdd230 100644 --- a/generation_config.yaml +++ b/generation_config.yaml @@ -1,6 +1,6 @@ gapic_generator_version: 2.53.0 -googleapis_commitish: a003cab30e2a263e16e9252256041f8934f40e2c -libraries_bom_version: 26.54.0 +googleapis_commitish: 3b2e8657f0bef4d9638f2957ed9d988adc65427c +libraries_bom_version: 26.55.0 libraries: - api_shortname: storage name_pretty: Cloud Storage From 8fe11a42a9013ee0484d19d1c9ccb6e0660c25be Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 24 Feb 2025 18:44:41 +0100 Subject: [PATCH 11/21] test(deps): update dependency org.junit:junit-bom to v5.12.0 (#2944) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cf4e8e8bf2..7dc31d200e 100644 --- a/pom.xml +++ b/pom.xml @@ -69,7 +69,7 @@ org.junit junit-bom - 5.11.4 + 5.12.0 pom import From 9af146d83df23dfd28553eff8b0d7c1834f071b6 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 24 Feb 2025 18:45:10 +0100 Subject: [PATCH 12/21] chore(deps): update dependency com.google.cloud:libraries-bom to v26.55.0 (#2945) --- samples/snippets/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 7a51759598..25f002fa3e 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -31,7 +31,7 @@ com.google.cloud libraries-bom - 26.54.0 + 26.55.0 pom import From 99d0f0cd24d8b63cdc20d1f4455adbeeeeeddb8b Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Mon, 24 Feb 2025 12:45:31 -0500 Subject: [PATCH 13/21] test: add slf4j and logback config for tests (#2946) --- google-cloud-storage/pom.xml | 28 ++++++ .../storage/it/runner/StorageITRunner.java | 9 ++ .../src/test/resources/logback.xml | 93 +++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 google-cloud-storage/src/test/resources/logback.xml diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index ec307a7ddb..c37a0008ec 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -334,6 +334,18 @@ 4.8.179 test + + org.slf4j + jul-to-slf4j + 2.0.16 + test + + + ch.qos.logback + logback-classic + 1.3.15 + test + @@ -386,6 +398,22 @@ --> io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi io.opentelemetry.semconv:opentelemetry-semconv + + org.slf4j:slf4j-api + org.slf4j:jul-to-slf4j + ch.qos.logback:logback-classic diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/StorageITRunner.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/StorageITRunner.java index 814c5da65c..d99df4b802 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/StorageITRunner.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/StorageITRunner.java @@ -42,6 +42,8 @@ import org.junit.runners.model.FrameworkField; import org.junit.runners.model.InitializationError; import org.junit.runners.model.TestClass; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Storage custom runner which will handle {@link CrossRun}, {@link SingleBackend}, {@link @@ -53,6 +55,12 @@ * @see org.junit.runners.BlockJUnit4ClassRunner */ public final class StorageITRunner extends Suite { + static { + org.slf4j.bridge.SLF4JBridgeHandler.removeHandlersForRootLogger(); + org.slf4j.bridge.SLF4JBridgeHandler.install(); + } + + private static final Logger LOGGER = LoggerFactory.getLogger(StorageITRunner.class); private final Lock childrenLock = new ReentrantLock(); private volatile ImmutableList filteredChildren = null; @@ -67,6 +75,7 @@ public StorageITRunner(Class klass) throws InitializationError { @Override public void run(RunNotifier notifier) { + LOGGER.debug("run(notifier : {})", notifier); super.run(new RunNotifierUnion(notifier, Registry.getInstance())); } diff --git a/google-cloud-storage/src/test/resources/logback.xml b/google-cloud-storage/src/test/resources/logback.xml new file mode 100644 index 0000000000..e97103c9b6 --- /dev/null +++ b/google-cloud-storage/src/test/resources/logback.xml @@ -0,0 +1,93 @@ + + + + + true + + + + %date %-5.5level [%-24.24thread] %-45.45logger{45} - %message%n + + + + + %date %-5.5level [%-24.24thread] %-45.45logger{45} - %message%nopex%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 95e540afe323bd70b087c8ce3106ba654b0d24bb Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Mon, 24 Feb 2025 15:57:29 -0500 Subject: [PATCH 14/21] chore: add renovate logging config (#2949) Update default log levels for http and grpc requests --- google-cloud-storage/src/test/resources/logback.xml | 4 ++-- renovate.json | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/google-cloud-storage/src/test/resources/logback.xml b/google-cloud-storage/src/test/resources/logback.xml index e97103c9b6..70cdedad80 100644 --- a/google-cloud-storage/src/test/resources/logback.xml +++ b/google-cloud-storage/src/test/resources/logback.xml @@ -78,8 +78,8 @@ - - + + diff --git a/renovate.json b/renovate.json index 48b00313a6..b6261a8427 100644 --- a/renovate.json +++ b/renovate.json @@ -94,7 +94,9 @@ "^org.mockito:mockito-core", "^org.objenesis:objenesis", "^com.google.cloud:google-cloud-conformance-tests", - "^io.github.classgraph:classgraph" + "^io.github.classgraph:classgraph", + "^ch.qos.logback:logback-classic", + "^org.slf4j:jul-to-slf4j" ], "semanticCommitType": "test", "semanticCommitScope": "deps" @@ -139,6 +141,12 @@ "^com.google.cloud:sdk-platform-java-config" ], "groupName": "sdk-platform-java dependencies" + }, + { + "packagePatterns": [ + "^ch.qos.logback:logback-classic" + ], + "allowedVersions": "<1.5.0" } ], "semanticCommits": true, From 51fcce1fb325e1eb6fa7942047ddff501f9e9d34 Mon Sep 17 00:00:00 2001 From: cloud-java-bot <122572305+cloud-java-bot@users.noreply.github.com> Date: Tue, 25 Feb 2025 11:15:47 -0500 Subject: [PATCH 15/21] chore: Update generation configuration at Tue Feb 25 02:24:21 UTC 2025 (#2951) * chore: Update generation configuration at Tue Feb 25 02:24:21 UTC 2025 * chore: generate libraries at Tue Feb 25 02:24:58 UTC 2025 --- README.md | 2 +- generation_config.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e44bc8f65c..87185f7ca4 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file: com.google.cloud libraries-bom - 26.54.0 + 26.55.0 pom import diff --git a/generation_config.yaml b/generation_config.yaml index c288bdd230..1a74239123 100644 --- a/generation_config.yaml +++ b/generation_config.yaml @@ -1,5 +1,5 @@ gapic_generator_version: 2.53.0 -googleapis_commitish: 3b2e8657f0bef4d9638f2957ed9d988adc65427c +googleapis_commitish: 6bc8e91bf92cc985da5ed0c227b48f12315cb695 libraries_bom_version: 26.55.0 libraries: - api_shortname: storage From cc4c7f411e7f7950594ac6910eb225ee1952196d Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Tue, 25 Feb 2025 11:38:08 -0500 Subject: [PATCH 16/21] chore: renovate config fix for ch.qos.logback:logback-classic (#2952) --- renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index b6261a8427..c4eb8ff74d 100644 --- a/renovate.json +++ b/renovate.json @@ -146,7 +146,7 @@ "packagePatterns": [ "^ch.qos.logback:logback-classic" ], - "allowedVersions": "<1.5.0" + "allowedVersions": "<1.4.0" } ], "semanticCommits": true, From 297802d1715e3289dd720fba851c563004b8c5f2 Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Tue, 25 Feb 2025 13:50:08 -0500 Subject: [PATCH 17/21] feat: add new Options to allow per method header values (#2941) Add new "Option"s for those methods which already have option types to allow providing an ImmutableMap to be applied as extra headers to all requests sent as part of that operation. If an operation has multiple sources of input Options (rewrite) the "first" (i.e. source option) will be the one added to the request. The following resources do not have "Option"s and therefor do not have extra headers support at this time: * Acl * DefaultAcl * ServiceAccount * Notification --- .../ApiaryUnbufferedReadableByteChannel.java | 13 +- .../google/cloud/storage/GrpcStorageImpl.java | 11 +- .../cloud/storage/GrpcStorageOptions.java | 8 +- .../cloud/storage/JsonResumableSession.java | 5 +- .../storage/JsonResumableSessionPutTask.java | 15 +- .../JsonResumableSessionQueryTask.java | 15 +- .../cloud/storage/JsonResumableWrite.java | 11 + .../ResumableSessionFailureScenario.java | 5 +- .../com/google/cloud/storage/Storage.java | 796 +++++++++++++++++- .../com/google/cloud/storage/UnifiedOpts.java | 196 ++++- .../java/com/google/cloud/storage/Utils.java | 5 + .../storage/XGoogApiClientHeaderProvider.java | 3 +- .../cloud/storage/spi/v1/HttpStorageRpc.java | 433 ++++++---- .../cloud/storage/spi/v1/StorageRpc.java | 7 +- .../storage/ITExtraHeadersOptionTest.java | 377 +++++++++ .../ITJsonResumableSessionPutTaskTest.java | 35 +- .../ITJsonResumableSessionQueryTaskTest.java | 15 +- .../cloud/storage/UnifiedOptsGrpcTest.java | 98 +++ .../storage/it/AssertRequestHeaders.java | 35 + .../cloud/storage/it/GrpcRequestAuditing.java | 39 +- .../cloud/storage/it/RequestAuditing.java | 29 +- 21 files changed, 1913 insertions(+), 238 deletions(-) create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/ITExtraHeadersOptionTest.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/it/AssertRequestHeaders.java diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedReadableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedReadableByteChannel.java index f61814d4f9..86afe7a98c 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedReadableByteChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedReadableByteChannel.java @@ -33,6 +33,7 @@ import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import com.google.common.io.BaseEncoding; @@ -51,8 +52,8 @@ import java.nio.channels.ReadableByteChannel; import java.nio.channels.ScatteringByteChannel; import java.util.List; -import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.function.Function; import javax.annotation.concurrent.Immutable; import org.checkerframework.checker.nullness.qual.NonNull; @@ -273,6 +274,14 @@ static Get createGetRequest( "x-goog-encryption-key-sha256", base64.encode(hashFunction.hashBytes(base64.decode(key)).asBytes())); }); + ifNonNull( + options.get(StorageRpc.Option.EXTRA_HEADERS), + ApiaryUnbufferedReadableByteChannel::cast, + (ImmutableMap extraHeaders) -> { + for (Entry e : extraHeaders.entrySet()) { + headers.set(e.getKey(), e.getValue()); + } + }); // gzip handling is performed upstream of here. Ensure we always get the raw input stream from // the request @@ -302,7 +311,7 @@ private static String getHeaderValue(@NonNull HttpHeaders headers, @NonNull Stri if (list.isEmpty()) { return null; } else { - return list.get(0).trim().toLowerCase(Locale.ENGLISH); + return Utils.headerNameToLowerCase(list.get(0).trim()); } } else if (o instanceof String) { return (String) o; diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java index f822130823..3211210fbc 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java @@ -650,8 +650,10 @@ public CopyWriter copy(CopyRequest copyRequest) { Opts dstOpts = Opts.unwrap(copyRequest.getTargetOptions()).resolveFrom(dst).prepend(defaultOpts); - Mapper mapper = + Mapper requestBuilderMapper = srcOpts.rewriteObjectsRequest().andThen(dstOpts.rewriteObjectsRequest()); + Mapper grpcCallContextMapper = + srcOpts.grpcMetadataMapper().andThen(dstOpts.grpcMetadataMapper()); Object srcProto = codecs.blobId().encode(src); Object dstProto = codecs.blobInfo().encode(dst); @@ -686,9 +688,8 @@ public CopyWriter copy(CopyRequest copyRequest) { b.setMaxBytesRewrittenPerCall(copyRequest.getMegabytesCopiedPerChunk() * _1MiB); } - RewriteObjectRequest req = mapper.apply(b).build(); - GrpcCallContext grpcCallContext = - srcOpts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); + RewriteObjectRequest req = requestBuilderMapper.apply(b).build(); + GrpcCallContext grpcCallContext = grpcCallContextMapper.apply(GrpcCallContext.createDefault()); UnaryCallable callable = storageClient.rewriteObjectCallable().withDefaultCallContext(grpcCallContext); GrpcCallContext retryContext = Retrying.newCallContext(); @@ -733,7 +734,7 @@ public GrpcBlobReadChannel reader(String bucket, String blob, BlobSourceOption.. public GrpcBlobReadChannel reader(BlobId blob, BlobSourceOption... options) { Opts opts = Opts.unwrap(options).resolveFrom(blob).prepend(defaultOpts); ReadObjectRequest request = getReadObjectRequest(blob, opts); - GrpcCallContext grpcCallContext = Retrying.newCallContext(); + GrpcCallContext grpcCallContext = opts.grpcMetadataMapper().apply(Retrying.newCallContext()); return new GrpcBlobReadChannel( storageClient.readObjectCallable().withDefaultCallContext(grpcCallContext), diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java index 279418c5f3..43e575a29d 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java @@ -96,7 +96,6 @@ import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; @@ -161,6 +160,11 @@ StorageSettings getStorageSettings() throws IOException { return resolveSettingsAndOpts().x(); } + @InternalApi + GrpcInterceptorProvider getGrpcInterceptorProvider() { + return grpcInterceptorProvider; + } + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); this.openTelemetry = HttpStorageOptions.getDefaultInstance().getOpenTelemetry(); @@ -225,7 +229,7 @@ private Tuple> resolveSettingsAndOpts() throw Map> requestMetadata = credentials.getRequestMetadata(uri); for (Entry> e : requestMetadata.entrySet()) { String key = e.getKey(); - if ("x-goog-user-project".equals(key.trim().toLowerCase(Locale.ENGLISH))) { + if ("x-goog-user-project".equals(Utils.headerNameToLowerCase(key.trim()))) { List value = e.getValue(); if (!value.isEmpty()) { foundQuotaProject = true; diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java index d997400dee..e29094fd09 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java @@ -54,14 +54,13 @@ final class JsonResumableSession { * have the concept of nested retry handling. */ ResumableOperationResult<@Nullable StorageObject> query() { - return new JsonResumableSessionQueryTask(context, resumableWrite.getUploadId()).call(); + return new JsonResumableSessionQueryTask(context, resumableWrite).call(); } ResumableOperationResult<@Nullable StorageObject> put( RewindableContent content, HttpContentRange contentRange) { JsonResumableSessionPutTask task = - new JsonResumableSessionPutTask( - context, resumableWrite.getUploadId(), content, contentRange); + new JsonResumableSessionPutTask(context, resumableWrite, content, contentRange); HttpRpcContext httpRpcContext = HttpRpcContext.getInstance(); try { httpRpcContext.newInvocationId(); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionPutTask.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionPutTask.java index 149c7ad9af..9ebd8e5868 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionPutTask.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionPutTask.java @@ -17,6 +17,7 @@ package com.google.cloud.storage; import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponseException; @@ -31,6 +32,7 @@ import java.io.IOException; import java.math.BigInteger; import java.util.Locale; +import java.util.Map.Entry; import java.util.concurrent.Callable; import org.checkerframework.checker.nullness.qual.Nullable; @@ -38,7 +40,7 @@ final class JsonResumableSessionPutTask implements Callable> { private final HttpClientContext context; - private final String uploadId; + private final JsonResumableWrite jsonResumableWrite; private final RewindableContent content; private final HttpContentRange originalContentRange; @@ -47,11 +49,11 @@ final class JsonResumableSessionPutTask @VisibleForTesting JsonResumableSessionPutTask( HttpClientContext httpClientContext, - String uploadId, + JsonResumableWrite jsonResumableWrite, RewindableContent content, HttpContentRange originalContentRange) { this.context = httpClientContext; - this.uploadId = uploadId; + this.jsonResumableWrite = jsonResumableWrite; this.content = content; this.originalContentRange = originalContentRange; this.contentRange = originalContentRange; @@ -87,13 +89,18 @@ public void rewindTo(long offset) { boolean success = false; boolean finalizing = originalContentRange.isFinalizing(); + String uploadId = jsonResumableWrite.getUploadId(); HttpRequest req = context .getRequestFactory() .buildPutRequest(new GenericUrl(uploadId), content) .setParser(context.getObjectParser()); req.setThrowExceptionOnExecuteError(false); - req.getHeaders().setContentRange(contentRange.getHeaderValue()); + HttpHeaders headers = req.getHeaders(); + headers.setContentRange(contentRange.getHeaderValue()); + for (Entry e : jsonResumableWrite.getExtraHeaders().entrySet()) { + headers.set(e.getKey(), e.getValue()); + } HttpResponse response = null; try { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionQueryTask.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionQueryTask.java index 57c5868c8e..dba12171ab 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionQueryTask.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionQueryTask.java @@ -20,6 +20,7 @@ import com.google.api.client.http.EmptyContent; import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponseException; @@ -27,6 +28,7 @@ import java.io.IOException; import java.math.BigInteger; import java.util.Locale; +import java.util.Map.Entry; import java.util.concurrent.Callable; import org.checkerframework.checker.nullness.qual.Nullable; @@ -34,15 +36,16 @@ final class JsonResumableSessionQueryTask implements Callable> { private final HttpClientContext context; - private final String uploadId; + private final JsonResumableWrite jsonResumableWrite; - JsonResumableSessionQueryTask(HttpClientContext context, String uploadId) { + JsonResumableSessionQueryTask(HttpClientContext context, JsonResumableWrite jsonResumableWrite) { this.context = context; - this.uploadId = uploadId; + this.jsonResumableWrite = jsonResumableWrite; } public ResumableOperationResult<@Nullable StorageObject> call() { HttpResponse response = null; + String uploadId = jsonResumableWrite.getUploadId(); try { HttpRequest req = context @@ -50,7 +53,11 @@ final class JsonResumableSessionQueryTask .buildPutRequest(new GenericUrl(uploadId), new EmptyContent()) .setParser(context.getObjectParser()); req.setThrowExceptionOnExecuteError(false); - req.getHeaders().setContentRange(HttpContentRange.query().getHeaderValue()); + HttpHeaders headers = req.getHeaders(); + headers.setContentRange(HttpContentRange.query().getHeaderValue()); + for (Entry e : jsonResumableWrite.getExtraHeaders().entrySet()) { + headers.set(e.getKey(), e.getValue()); + } response = req.execute(); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java index 336ce0e477..41bbec72ec 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java @@ -21,6 +21,7 @@ import com.google.api.services.storage.model.StorageObject; import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.stream.JsonReader; import java.io.IOException; @@ -60,6 +61,16 @@ private JsonResumableWrite( this.beginOffset = beginOffset; } + ImmutableMap getExtraHeaders() { + if (options != null) { + Object tmp = options.get(StorageRpc.Option.EXTRA_HEADERS); + if (tmp != null) { + return (ImmutableMap) tmp; + } + } + return ImmutableMap.of(); + } + public @NonNull String getUploadId() { return uploadId; } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSessionFailureScenario.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSessionFailureScenario.java index aab5263397..390b90dcfd 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSessionFailureScenario.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSessionFailureScenario.java @@ -35,7 +35,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.function.Consumer; import java.util.function.Predicate; @@ -295,12 +294,12 @@ static boolean isContinue(int code) { // The header names from HttpHeaders are lower cased, define some utility methods to create // predicates where we can specify values ignoring case private static Predicate matches(String expected) { - String lower = expected.toLowerCase(Locale.US); + String lower = Utils.headerNameToLowerCase(expected); return lower::equals; } private static Predicate startsWith(String prefix) { - String lower = prefix.toLowerCase(Locale.US); + String lower = Utils.headerNameToLowerCase(prefix); return s -> s.startsWith(lower); } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 8d6f479eb9..6ed3c5af88 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -471,6 +471,59 @@ public static BucketTargetOption projection(@NonNull String projection) { return new BucketTargetOption(UnifiedOpts.projection(projection)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BucketTargetOption extraHeaders( + @NonNull ImmutableMap extraHeaders) { + return new BucketTargetOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -545,6 +598,59 @@ public static BucketSourceOption requestedPolicyVersion(long version) { return new BucketSourceOption(UnifiedOpts.requestedPolicyVersion(version)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BucketSourceOption extraHeaders( + @NonNull ImmutableMap extraHeaders) { + return new BucketSourceOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -633,6 +739,59 @@ public static ListHmacKeysOption projectId(@NonNull String projectId) { return new ListHmacKeysOption(UnifiedOpts.projectId(projectId)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static ListHmacKeysOption extraHeaders( + @NonNull ImmutableMap extraHeaders) { + return new ListHmacKeysOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -691,6 +850,59 @@ public static CreateHmacKeyOption projectId(@NonNull String projectId) { return new CreateHmacKeyOption(UnifiedOpts.projectId(projectId)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static CreateHmacKeyOption extraHeaders( + @NonNull ImmutableMap extraHeaders) { + return new CreateHmacKeyOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -726,6 +938,7 @@ public static CreateHmacKeyOption[] dedupe( /** Class for specifying getHmacKey options */ class GetHmacKeyOption extends Option { + private GetHmacKeyOption(HmacKeySourceOpt opt) { super(opt); } @@ -748,6 +961,59 @@ public static GetHmacKeyOption projectId(@NonNull String projectId) { return new GetHmacKeyOption(UnifiedOpts.projectId(projectId)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static GetHmacKeyOption extraHeaders( + @NonNull ImmutableMap extraHeaders) { + return new GetHmacKeyOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -782,6 +1048,7 @@ public static GetHmacKeyOption[] dedupe(GetHmacKeyOption[] array, GetHmacKeyOpti /** Class for specifying deleteHmacKey options */ class DeleteHmacKeyOption extends Option { + private DeleteHmacKeyOption(HmacKeyTargetOpt opt) { super(opt); } @@ -795,6 +1062,59 @@ public static DeleteHmacKeyOption userProject(@NonNull String userProject) { return new DeleteHmacKeyOption(UnifiedOpts.userProject(userProject)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static DeleteHmacKeyOption extraHeaders( + @NonNull ImmutableMap extraHeaders) { + return new DeleteHmacKeyOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -830,6 +1150,7 @@ public static DeleteHmacKeyOption[] dedupe( /** Class for specifying updateHmacKey options */ class UpdateHmacKeyOption extends Option { + private UpdateHmacKeyOption(HmacKeyTargetOpt opt) { super(opt); } @@ -843,6 +1164,59 @@ public static UpdateHmacKeyOption userProject(@NonNull String userProject) { return new UpdateHmacKeyOption(UnifiedOpts.userProject(userProject)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static UpdateHmacKeyOption extraHeaders( + @NonNull ImmutableMap extraHeaders) { + return new UpdateHmacKeyOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -929,6 +1303,58 @@ public static BucketGetOption fields(BucketField... fields) { return new BucketGetOption(UnifiedOpts.fields(set)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BucketGetOption extraHeaders(@NonNull ImmutableMap extraHeaders) { + return new BucketGetOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -1120,6 +1546,59 @@ public static BlobTargetOption overrideUnlockedRetention(boolean overrideUnlocke return new BlobTargetOption(UnifiedOpts.overrideUnlockedRetention(overrideUnlockedRetention)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BlobTargetOption extraHeaders( + @NonNull ImmutableMap extraHeaders) { + return new BlobTargetOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -1346,6 +1825,58 @@ public static BlobWriteOption expectedObjectSize(long objectContentSize) { return new BlobWriteOption(UnifiedOpts.resumableUploadExpectedObjectSize(objectContentSize)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BlobWriteOption extraHeaders(@NonNull ImmutableMap extraHeaders) { + return new BlobWriteOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -1490,6 +2021,59 @@ public static BlobSourceOption shouldReturnRawInputStream(boolean shouldReturnRa return new BlobSourceOption(UnifiedOpts.returnRawInputStream(shouldReturnRawInputStream)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BlobSourceOption extraHeaders( + @NonNull ImmutableMap extraHeaders) { + return new BlobSourceOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -1658,6 +2242,58 @@ public static BlobGetOption softDeleted(boolean softDeleted) { return new BlobGetOption(UnifiedOpts.softDeleted(softDeleted)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BlobGetOption extraHeaders(@NonNull ImmutableMap extraHeaders) { + return new BlobGetOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -1743,6 +2379,59 @@ public static BlobRestoreOption metagenerationNotMatch(long generation) { public static BlobRestoreOption copySourceAcl(boolean copySourceAcl) { return new BlobRestoreOption(UnifiedOpts.copySourceAcl(copySourceAcl)); } + + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BlobRestoreOption extraHeaders( + @NonNull ImmutableMap extraHeaders) { + return new BlobRestoreOption(UnifiedOpts.extraHeaders(extraHeaders)); + } } /** Class for specifying bucket list options. */ @@ -1802,6 +2491,59 @@ public static BucketListOption fields(BucketField... fields) { return new BucketListOption(UnifiedOpts.fields(set)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BucketListOption extraHeaders( + @NonNull ImmutableMap extraHeaders) { + return new BucketListOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -1979,6 +2721,58 @@ public static BlobListOption softDeleted(boolean softDeleted) { return new BlobListOption(UnifiedOpts.softDeleted(softDeleted)); } + /** + * A set of extra headers to be set for all requests performed within the scope of the operation + * this option is passed to (a get, read, resumable upload etc). + * + *

If the same header name is specified across multiple options provided to a method, the + * first occurrence will be the value included in the request(s). + * + *

The following headers are not allowed to be specified, and will result in an {@link + * IllegalArgumentException}. + * + *

    + *
  1. {@code Accept-Encoding} + *
  2. {@code Cache-Control} + *
  3. {@code Connection} + *
  4. {@code Content-ID} + *
  5. {@code Content-Length} + *
  6. {@code Content-Range} + *
  7. {@code Content-Transfer-Encoding} + *
  8. {@code Content-Type} + *
  9. {@code Date} + *
  10. {@code ETag} + *
  11. {@code If-Match} + *
  12. {@code If-None-Match} + *
  13. {@code Keep-Alive} + *
  14. {@code Range} + *
  15. {@code TE} + *
  16. {@code Trailer} + *
  17. {@code Transfer-Encoding} + *
  18. {@code User-Agent} + *
  19. {@code X-Goog-Api-Client} + *
  20. {@code X-Goog-Content-Length-Range} + *
  21. {@code X-Goog-Copy-Source-Encryption-Algorithm} + *
  22. {@code X-Goog-Copy-Source-Encryption-Key} + *
  23. {@code X-Goog-Copy-Source-Encryption-Key-Sha256} + *
  24. {@code X-Goog-Encryption-Algorithm} + *
  25. {@code X-Goog-Encryption-Key} + *
  26. {@code X-Goog-Encryption-Key-Sha256} + *
  27. {@code X-Goog-Gcs-Idempotency-Token} + *
  28. {@code X-Goog-Meta-*} + *
  29. {@code X-Goog-User-Project} + *
  30. {@code X-HTTP-Method-Override} + *
  31. {@code X-Upload-Content-Length} + *
  32. {@code X-Upload-Content-Type} + *
+ * + * @since 2.49.0 + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BlobListOption extraHeaders(@NonNull ImmutableMap extraHeaders) { + return new BlobListOption(UnifiedOpts.extraHeaders(extraHeaders)); + } + /** * Deduplicate any options which are the same parameter. The value which comes last in {@code * os} will be the value included in the return. @@ -5036,8 +5830,6 @@ default BlobWriteSession blobWriteSession(BlobInfo blobInfo, BlobWriteOption... * {@link Storage#delete(BlobId)}, however without the ability to change metadata fields for the * target object. * - *

This feature is currently only supported for HNS (Hierarchical Namespace) buckets. - * * @since 2.48.0 */ @TransportCompatibility({Transport.HTTP, Transport.GRPC}) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java index 6c9828c1b1..09a335f35a 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java @@ -62,7 +62,10 @@ import java.security.Key; import java.util.Arrays; import java.util.Collection; -import java.util.Locale; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; @@ -565,6 +568,18 @@ static Md5MatchExtractor md5MatchExtractor() { return Md5MatchExtractor.INSTANCE; } + static Headers extraHeaders(ImmutableMap extraHeaders) { + requireNonNull(extraHeaders, "extraHeaders must be non null"); + String blockedHeaders = + extraHeaders.keySet().stream() + .map(Utils::headerNameToLowerCase) + .filter(Headers.BLOCKLIST) + .sorted(Comparator.naturalOrder()) + .collect(Collectors.joining(", ", "[", "]")); + checkArgument("[]".equals(blockedHeaders), "Disallowed headers: %s", blockedHeaders); + return new Headers(extraHeaders); + } + static final class Crc32cMatch implements ObjectTargetOpt { private static final long serialVersionUID = 8172282701777561769L; private final int val; @@ -1920,6 +1935,183 @@ public Mapper moveObject() { } } + static final class Headers extends RpcOptVal> + implements BucketSourceOpt, + BucketTargetOpt, + BucketListOpt, + ObjectSourceOpt, + ObjectTargetOpt, + ObjectListOpt, + HmacKeySourceOpt, + HmacKeyTargetOpt, + HmacKeyListOpt { + + /** + * The set of header names which are blocked from being able to be provided for an instance of + * this class. + * + *

Most values here are from the json api + * parameters list or general http headers our client otherwise sets during the course of + * normal operation. + */ + private static final Predicate BLOCKLIST; + + static { + ImmutableSet fullHeaderNames = + Stream.of( + "Accept-Encoding", + "Cache-Control", + "Connection", + "Content-ID", + "Content-Length", + "Content-Range", + "Content-Transfer-Encoding", + "Content-Type", + "Date", + "ETag", + "If-Match", + "If-None-Match", + "Keep-Alive", + "Range", + "TE", + "Trailer", + "Transfer-Encoding", + "User-Agent", + "X-Goog-Api-Client", + "X-Goog-Content-Length-Range", + "X-Goog-Copy-Source-Encryption-Algorithm", + "X-Goog-Copy-Source-Encryption-Key", + "X-Goog-Copy-Source-Encryption-Key-Sha256", + "X-Goog-Encryption-Algorithm", + "X-Goog-Encryption-Key", + "X-Goog-Encryption-Key-Sha256", + "X-Goog-Gcs-Idempotency-Token", + "X-Goog-Request-Params", + "X-Goog-User-Project", + "X-HTTP-Method-Override", + "X-Upload-Content-Length", + "X-Upload-Content-Type") + .map(Utils::headerNameToLowerCase) + .collect(ImmutableSet.toImmutableSet()); + + ImmutableSet prefixes = + Stream.of("X-Goog-Meta-") + .map(Utils::headerNameToLowerCase) + .collect(ImmutableSet.toImmutableSet()); + + BLOCKLIST = + name -> { + if (fullHeaderNames.contains(name)) { + return true; + } + + for (String prefix : prefixes) { + if (name.startsWith(prefix)) { + return true; + } + } + return false; + }; + } + + private Headers(ImmutableMap val) { + super(StorageRpc.Option.EXTRA_HEADERS, val); + } + + @Override + public Mapper getGrpcMetadataMapper() { + return ctx -> { + if (val.isEmpty()) { + return ctx; + } + Set existingHeaderNames = + ctx.getExtraHeaders().keySet().stream() + .map(Utils::headerNameToLowerCase) + .collect(Collectors.toSet()); + Map> wrapped = new HashMap<>(); + for (Entry e : val.entrySet()) { + String key = Utils.headerNameToLowerCase(e.getKey()); + if (existingHeaderNames.contains(key)) { + continue; + } + wrapped.put(key, ImmutableList.of(e.getValue())); + } + return ctx.withExtraHeaders(wrapped); + }; + } + + @SuppressWarnings("unchecked") + @Override + public Mapper> mapper() { + return optionBuilder -> { + if (val.isEmpty()) { + return optionBuilder; + } + // not ideal, but ImmutableMap.Builder doesn't have any read methods so we can detect + // collision before build time. + ImmutableMap builtOptions = Utils.mapBuild(optionBuilder); + ImmutableMap tmp = + (ImmutableMap) builtOptions.get(StorageRpc.Option.EXTRA_HEADERS); + if (tmp == null) { + ImmutableMap.Builder b = ImmutableMap.builder(); + for (Entry e : val.entrySet()) { + String key = Utils.headerNameToLowerCase(e.getKey()); + b.put(key, e.getValue()); + } + optionBuilder.put(key, Utils.mapBuild(b)); + return optionBuilder; + } else { + ImmutableMap.Builder newOptionBuilder = ImmutableMap.builder(); + for (Entry e : builtOptions.entrySet()) { + if (e.getKey() != key) { + newOptionBuilder.put(e.getKey(), e.getValue()); + } + } + + ImmutableMap.Builder extraHeadersBuilder = ImmutableMap.builder(); + copyEntries(tmp, extraHeadersBuilder); + newOptionBuilder.put(key, Utils.mapBuild(extraHeadersBuilder)); + return newOptionBuilder; + } + }; + } + + public Mapper> extraHeadersMapper() { + return map -> { + if (val.isEmpty()) { + return map; + } + ImmutableMap.Builder b = ImmutableMap.builder(); + copyEntries(map, b); + return Utils.mapBuild(b); + }; + } + + private void copyEntries( + ImmutableMap map, ImmutableMap.Builder b) { + Set existingHeaderNames = + map.keySet().stream().map(Utils::headerNameToLowerCase).collect(Collectors.toSet()); + b.putAll(map); + for (Entry e : val.entrySet()) { + String key = Utils.headerNameToLowerCase(e.getKey()); + if (!existingHeaderNames.contains(key)) { + b.put(key, e.getValue()); + } + } + } + + @Override + public Mapper rewriteObject() { + return Mapper.identity(); + } + + @Override + public Mapper moveObject() { + return Mapper.identity(); + } + } + static final class VersionsFilter extends RpcOptVal<@NonNull Boolean> implements ObjectListOpt { private VersionsFilter(boolean val) { super(StorageRpc.Option.VERSIONS, val); @@ -2003,7 +2195,7 @@ public ObjectTargetOpt extractFromBlobId(BlobId id) { private ObjectTargetOpt detectForName(String name) { if (name != null) { - String nameLower = name.toLowerCase(Locale.ENGLISH); + String nameLower = Utils.headerNameToLowerCase(name); String contentTypeFor = FILE_NAME_MAP.getContentTypeFor(nameLower); if (contentTypeFor != null) { return new SetContentType(contentTypeFor); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java index 853294c1f3..a2de1ad593 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java @@ -40,6 +40,7 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.Arrays; +import java.util.Locale; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -319,4 +320,8 @@ static ImmutableList nullSafeList(@Nullable T t) { static ImmutableMap mapBuild(ImmutableMap.Builder b) { return (ImmutableMap) mapBuild.apply(b); } + + static String headerNameToLowerCase(String headerName) { + return headerName.toLowerCase(Locale.US); + } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/XGoogApiClientHeaderProvider.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/XGoogApiClientHeaderProvider.java index 263a670582..310c2b12ed 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/XGoogApiClientHeaderProvider.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/XGoogApiClientHeaderProvider.java @@ -24,7 +24,6 @@ import com.google.common.collect.MapDifference.ValueDifference; import com.google.common.collect.Maps; import java.util.HashMap; -import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collector; @@ -98,7 +97,7 @@ private static Map lowerKeys(Map orig) { for (Entry e : orig.entrySet()) { String k = e.getKey(); String v = e.getValue(); - tmp.put(k.toLowerCase(Locale.US), v); + tmp.put(Utils.headerNameToLowerCase(k), v); } return tmp; } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java index fc8553e7e3..286654deaa 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java @@ -43,17 +43,28 @@ import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.util.Data; import com.google.api.services.storage.Storage; +import com.google.api.services.storage.Storage.BucketAccessControls; +import com.google.api.services.storage.Storage.Buckets; +import com.google.api.services.storage.Storage.Buckets.LockRetentionPolicy; +import com.google.api.services.storage.Storage.Buckets.SetIamPolicy; +import com.google.api.services.storage.Storage.Buckets.TestIamPermissions; +import com.google.api.services.storage.Storage.DefaultObjectAccessControls; +import com.google.api.services.storage.Storage.Notifications; +import com.google.api.services.storage.Storage.ObjectAccessControls; import com.google.api.services.storage.Storage.Objects.Compose; import com.google.api.services.storage.Storage.Objects.Delete; import com.google.api.services.storage.Storage.Objects.Get; import com.google.api.services.storage.Storage.Objects.Insert; import com.google.api.services.storage.Storage.Objects.Move; import com.google.api.services.storage.Storage.Objects.Patch; +import com.google.api.services.storage.Storage.Projects; +import com.google.api.services.storage.Storage.Projects.HmacKeys; +import com.google.api.services.storage.Storage.Projects.HmacKeys.Create; +import com.google.api.services.storage.Storage.Projects.HmacKeys.Update; import com.google.api.services.storage.StorageRequest; import com.google.api.services.storage.model.Bucket; import com.google.api.services.storage.model.Bucket.RetentionPolicy; import com.google.api.services.storage.model.BucketAccessControl; -import com.google.api.services.storage.model.Buckets; import com.google.api.services.storage.model.ComposeRequest; import com.google.api.services.storage.model.ComposeRequest.SourceObjects.ObjectPreconditions; import com.google.api.services.storage.model.HmacKey; @@ -73,6 +84,7 @@ import com.google.cloud.storage.StorageOptions; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.hash.HashFunction; @@ -97,6 +109,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.UUID; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -375,15 +388,18 @@ public Bucket create(Bucket bucket, Map options) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_BUCKET); Scope scope = tracer.withSpan(span); try { - return storage - .buckets() - .insert(this.options.getProjectId(), bucket) - .setProjection(DEFAULT_PROJECTION) - .setPredefinedAcl(Option.PREDEFINED_ACL.getString(options)) - .setPredefinedDefaultObjectAcl(Option.PREDEFINED_DEFAULT_OBJECT_ACL.getString(options)) - .setUserProject(Option.USER_PROJECT.getString(options)) - .setEnableObjectRetention(Option.ENABLE_OBJECT_RETENTION.getBoolean(options)) - .execute(); + Storage.Buckets.Insert insert = + storage + .buckets() + .insert(this.options.getProjectId(), bucket) + .setProjection(DEFAULT_PROJECTION) + .setPredefinedAcl(Option.PREDEFINED_ACL.getString(options)) + .setPredefinedDefaultObjectAcl( + Option.PREDEFINED_DEFAULT_OBJECT_ACL.getString(options)) + .setUserProject(Option.USER_PROJECT.getString(options)) + .setEnableObjectRetention(Option.ENABLE_OBJECT_RETENTION.getBoolean(options)); + setExtraHeaders(insert, options); + return insert.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -412,6 +428,7 @@ public StorageObject create( insert.setDisableGZipContent(disableGzipContent); } setEncryptionHeaders(insert.getRequestHeaders(), ENCRYPTION_KEY_PREFIX, options); + setExtraHeaders(insert, options); return insert .setProjection(DEFAULT_PROJECTION) .setPredefinedAcl(Option.PREDEFINED_ACL.getString(options)) @@ -436,7 +453,7 @@ public Tuple> list(Map options) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_LIST_BUCKETS); Scope scope = tracer.withSpan(span); try { - Buckets buckets = + Storage.Buckets.List list = storage .buckets() .list(this.options.getProjectId()) @@ -445,8 +462,9 @@ public Tuple> list(Map options) { .setMaxResults(Option.MAX_RESULTS.getLong(options)) .setPageToken(Option.PAGE_TOKEN.getString(options)) .setFields(Option.FIELDS.getString(options)) - .setUserProject(Option.USER_PROJECT.getString(options)) - .execute(); + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(list, options); + com.google.api.services.storage.model.Buckets buckets = list.execute(); return Tuple.>of(buckets.getNextPageToken(), buckets.getItems()); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); @@ -462,7 +480,7 @@ public Tuple> list(final String bucket, Map> list(final String bucket, Map storageObjects = Iterables.concat( firstNonNull(objects.getItems(), ImmutableList.of()), @@ -503,7 +522,7 @@ private static String detectContentType(StorageObject object, Map opt } if (Boolean.TRUE == Option.DETECT_CONTENT_TYPE.get(options)) { - contentType = FILE_NAME_MAP.getContentTypeFor(object.getName().toLowerCase(Locale.ENGLISH)); + contentType = FILE_NAME_MAP.getContentTypeFor(object.getName().toLowerCase(Locale.US)); } return firstNonNull(contentType, "application/octet-stream"); @@ -527,15 +546,17 @@ public Bucket get(Bucket bucket, Map options) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_GET_BUCKET); Scope scope = tracer.withSpan(span); try { - return storage - .buckets() - .get(bucket.getName()) - .setProjection(DEFAULT_PROJECTION) - .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) - .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(options)) - .setFields(Option.FIELDS.getString(options)) - .setUserProject(Option.USER_PROJECT.getString(options)) - .execute(); + Storage.Buckets.Get get = + storage + .buckets() + .get(bucket.getName()) + .setProjection(DEFAULT_PROJECTION) + .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) + .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(options)) + .setFields(Option.FIELDS.getString(options)) + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(get, options); + return get.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); StorageException serviceException = translate(ex); @@ -553,6 +574,7 @@ private Storage.Objects.Get getCall(StorageObject object, Map options throws IOException { Storage.Objects.Get get = storage.objects().get(object.getBucket(), object.getName()); setEncryptionHeaders(get.getRequestHeaders(), ENCRYPTION_KEY_PREFIX, options); + setExtraHeaders(get, options); return get.setGeneration(object.getGeneration()) .setProjection(DEFAULT_PROJECTION) .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) @@ -590,6 +612,7 @@ public StorageObject restore(StorageObject object, Map options) { try { Storage.Objects.Restore restore = storage.objects().restore(object.getBucket(), object.getName(), object.getGeneration()); + setExtraHeaders(restore, options); return restore .setProjection(DEFAULT_PROJECTION) .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) @@ -644,16 +667,19 @@ public Bucket patch(Bucket bucket, Map options) { projection = NO_ACL_PROJECTION; } } - return storage - .buckets() - .patch(bucket.getName(), bucket) - .setProjection(projection == null ? DEFAULT_PROJECTION : projection) - .setPredefinedAcl(Option.PREDEFINED_ACL.getString(options)) - .setPredefinedDefaultObjectAcl(Option.PREDEFINED_DEFAULT_OBJECT_ACL.getString(options)) - .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) - .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(options)) - .setUserProject(Option.USER_PROJECT.getString(options)) - .execute(); + Buckets.Patch patch = + storage + .buckets() + .patch(bucket.getName(), bucket) + .setProjection(projection == null ? DEFAULT_PROJECTION : projection) + .setPredefinedAcl(Option.PREDEFINED_ACL.getString(options)) + .setPredefinedDefaultObjectAcl( + Option.PREDEFINED_DEFAULT_OBJECT_ACL.getString(options)) + .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) + .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(options)) + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(patch, options); + return patch.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -665,17 +691,20 @@ public Bucket patch(Bucket bucket, Map options) { private Storage.Objects.Patch patchCall(StorageObject storageObject, Map options) throws IOException { - return storage - .objects() - .patch(storageObject.getBucket(), storageObject.getName(), storageObject) - .setProjection(DEFAULT_PROJECTION) - .setPredefinedAcl(Option.PREDEFINED_ACL.getString(options)) - .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) - .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(options)) - .setIfGenerationMatch(Option.IF_GENERATION_MATCH.getLong(options)) - .setIfGenerationNotMatch(Option.IF_GENERATION_NOT_MATCH.getLong(options)) - .setOverrideUnlockedRetention(Option.OVERRIDE_UNLOCKED_RETENTION.getBoolean(options)) - .setUserProject(Option.USER_PROJECT.getString(options)); + Storage.Objects.Patch patch = + storage + .objects() + .patch(storageObject.getBucket(), storageObject.getName(), storageObject) + .setProjection(DEFAULT_PROJECTION) + .setPredefinedAcl(Option.PREDEFINED_ACL.getString(options)) + .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) + .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(options)) + .setIfGenerationMatch(Option.IF_GENERATION_MATCH.getLong(options)) + .setIfGenerationNotMatch(Option.IF_GENERATION_NOT_MATCH.getLong(options)) + .setOverrideUnlockedRetention(Option.OVERRIDE_UNLOCKED_RETENTION.getBoolean(options)) + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(patch, options); + return patch; } @Override @@ -698,13 +727,15 @@ public boolean delete(Bucket bucket, Map options) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_DELETE_BUCKET); Scope scope = tracer.withSpan(span); try { - storage - .buckets() - .delete(bucket.getName()) - .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) - .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(options)) - .setUserProject(Option.USER_PROJECT.getString(options)) - .execute(); + Buckets.Delete delete = + storage + .buckets() + .delete(bucket.getName()) + .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) + .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(options)) + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(delete, options); + delete.execute(); return true; } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); @@ -721,15 +752,18 @@ public boolean delete(Bucket bucket, Map options) { private Storage.Objects.Delete deleteCall(StorageObject blob, Map options) throws IOException { - return storage - .objects() - .delete(blob.getBucket(), blob.getName()) - .setGeneration(blob.getGeneration()) - .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) - .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(options)) - .setIfGenerationMatch(Option.IF_GENERATION_MATCH.getLong(options)) - .setIfGenerationNotMatch(Option.IF_GENERATION_NOT_MATCH.getLong(options)) - .setUserProject(Option.USER_PROJECT.getString(options)); + Storage.Objects.Delete delete = + storage + .objects() + .delete(blob.getBucket(), blob.getName()) + .setGeneration(blob.getGeneration()) + .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) + .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(options)) + .setIfGenerationMatch(Option.IF_GENERATION_MATCH.getLong(options)) + .setIfGenerationNotMatch(Option.IF_GENERATION_NOT_MATCH.getLong(options)) + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(delete, options); + return delete; } @Override @@ -781,6 +815,7 @@ public StorageObject compose( .setIfGenerationMatch(Option.IF_GENERATION_MATCH.getLong(targetOptions)) .setUserProject(Option.USER_PROJECT.getString(targetOptions)); setEncryptionHeaders(compose.getRequestHeaders(), ENCRYPTION_KEY_PREFIX, targetOptions); + setExtraHeaders(compose, targetOptions); return compose.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); @@ -807,6 +842,7 @@ public byte[] load(StorageObject from, Map options) { .setIfGenerationNotMatch(Option.IF_GENERATION_NOT_MATCH.getLong(options)) .setUserProject(Option.USER_PROJECT.getString(options)); setEncryptionHeaders(getRequest.getRequestHeaders(), ENCRYPTION_KEY_PREFIX, options); + setExtraHeaders(getRequest, options); if (Option.RETURN_RAW_INPUT_STREAM.getBoolean(options) != null) { getRequest.setReturnRawInputStream(Option.RETURN_RAW_INPUT_STREAM.getBoolean(options)); } @@ -846,6 +882,7 @@ private Get createReadRequest(StorageObject from, Map options) throws .setIfGenerationNotMatch(Option.IF_GENERATION_NOT_MATCH.getLong(options)) .setUserProject(Option.USER_PROJECT.getString(options)); setEncryptionHeaders(req.getRequestHeaders(), ENCRYPTION_KEY_PREFIX, options); + setExtraHeaders(req, options); return req; } @@ -1112,6 +1149,7 @@ public String open(StorageObject object, Map options) { requestHeaders.set("X-Upload-Content-Length", xUploadContentLength); } setEncryptionHeaders(requestHeaders, "x-goog-encryption-", options); + setExtraHeaders(Option.EXTRA_HEADERS.get(options), requestHeaders); HttpResponse response = httpRequest.execute(); if (response.getStatusCode() != 200) { throw buildStorageException(response.getStatusCode(), response.getStatusMessage()); @@ -1263,6 +1301,8 @@ private RewriteResponse rewrite(RewriteRequest req, String token) { HttpHeaders requestHeaders = rewrite.getRequestHeaders(); setEncryptionHeaders(requestHeaders, SOURCE_ENCRYPTION_KEY_PREFIX, req.sourceOptions); setEncryptionHeaders(requestHeaders, ENCRYPTION_KEY_PREFIX, req.targetOptions); + setExtraHeaders(rewrite, req.sourceOptions); + setExtraHeaders(rewrite, req.targetOptions); com.google.api.services.storage.model.RewriteResponse rewriteResponse = rewrite.execute(); return new RewriteResponse( req, @@ -1282,11 +1322,13 @@ public BucketAccessControl getAcl(String bucket, String entity, Map o Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_GET_BUCKET_ACL); Scope scope = tracer.withSpan(span); try { - return storage - .bucketAccessControls() - .get(bucket, entity) - .setUserProject(Option.USER_PROJECT.getString(options)) - .execute(); + BucketAccessControls.Get get = + storage + .bucketAccessControls() + .get(bucket, entity) + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(get, options); + return get.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); StorageException serviceException = translate(ex); @@ -1305,11 +1347,13 @@ public boolean deleteAcl(String bucket, String entity, Map options) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_DELETE_BUCKET_ACL); Scope scope = tracer.withSpan(span); try { - storage - .bucketAccessControls() - .delete(bucket, entity) - .setUserProject(Option.USER_PROJECT.getString(options)) - .execute(); + BucketAccessControls.Delete delete = + storage + .bucketAccessControls() + .delete(bucket, entity) + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(delete, options); + delete.execute(); return true; } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); @@ -1329,11 +1373,13 @@ public BucketAccessControl createAcl(BucketAccessControl acl, Map opt Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_BUCKET_ACL); Scope scope = tracer.withSpan(span); try { - return storage - .bucketAccessControls() - .insert(acl.getBucket(), acl) - .setUserProject(Option.USER_PROJECT.getString(options)) - .execute(); + BucketAccessControls.Insert insert = + storage + .bucketAccessControls() + .insert(acl.getBucket(), acl) + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(insert, options); + return insert.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -1348,11 +1394,13 @@ public BucketAccessControl patchAcl(BucketAccessControl acl, Map opti Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_PATCH_BUCKET_ACL); Scope scope = tracer.withSpan(span); try { - return storage - .bucketAccessControls() - .patch(acl.getBucket(), acl.getEntity(), acl) - .setUserProject(Option.USER_PROJECT.getString(options)) - .execute(); + BucketAccessControls.Patch patch = + storage + .bucketAccessControls() + .patch(acl.getBucket(), acl.getEntity(), acl) + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(patch, options); + return patch.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -1367,12 +1415,13 @@ public List listAcls(String bucket, Map options) Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_LIST_BUCKET_ACLS); Scope scope = tracer.withSpan(span); try { - return storage - .bucketAccessControls() - .list(bucket) - .setUserProject(Option.USER_PROJECT.getString(options)) - .execute() - .getItems(); + BucketAccessControls.List list = + storage + .bucketAccessControls() + .list(bucket) + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(list, options); + return list.execute().getItems(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -1387,7 +1436,9 @@ public ObjectAccessControl getDefaultAcl(String bucket, String entity) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_GET_OBJECT_DEFAULT_ACL); Scope scope = tracer.withSpan(span); try { - return storage.defaultObjectAccessControls().get(bucket, entity).execute(); + DefaultObjectAccessControls.Get get = + storage.defaultObjectAccessControls().get(bucket, entity); + return get.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); StorageException serviceException = translate(ex); @@ -1406,7 +1457,9 @@ public boolean deleteDefaultAcl(String bucket, String entity) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_DELETE_OBJECT_DEFAULT_ACL); Scope scope = tracer.withSpan(span); try { - storage.defaultObjectAccessControls().delete(bucket, entity).execute(); + DefaultObjectAccessControls.Delete delete = + storage.defaultObjectAccessControls().delete(bucket, entity); + delete.execute(); return true; } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); @@ -1426,7 +1479,9 @@ public ObjectAccessControl createDefaultAcl(ObjectAccessControl acl) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_OBJECT_DEFAULT_ACL); Scope scope = tracer.withSpan(span); try { - return storage.defaultObjectAccessControls().insert(acl.getBucket(), acl).execute(); + DefaultObjectAccessControls.Insert insert = + storage.defaultObjectAccessControls().insert(acl.getBucket(), acl); + return insert.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -1441,10 +1496,9 @@ public ObjectAccessControl patchDefaultAcl(ObjectAccessControl acl) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_PATCH_OBJECT_DEFAULT_ACL); Scope scope = tracer.withSpan(span); try { - return storage - .defaultObjectAccessControls() - .patch(acl.getBucket(), acl.getEntity(), acl) - .execute(); + DefaultObjectAccessControls.Patch patch = + storage.defaultObjectAccessControls().patch(acl.getBucket(), acl.getEntity(), acl); + return patch.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -1459,7 +1513,8 @@ public List listDefaultAcls(String bucket) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_LIST_OBJECT_DEFAULT_ACLS); Scope scope = tracer.withSpan(span); try { - return storage.defaultObjectAccessControls().list(bucket).execute().getItems(); + DefaultObjectAccessControls.List list = storage.defaultObjectAccessControls().list(bucket); + return list.execute().getItems(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -1474,11 +1529,9 @@ public ObjectAccessControl getAcl(String bucket, String object, Long generation, Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_GET_OBJECT_ACL); Scope scope = tracer.withSpan(span); try { - return storage - .objectAccessControls() - .get(bucket, object, entity) - .setGeneration(generation) - .execute(); + ObjectAccessControls.Get get = + storage.objectAccessControls().get(bucket, object, entity).setGeneration(generation); + return get.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); StorageException serviceException = translate(ex); @@ -1497,11 +1550,9 @@ public boolean deleteAcl(String bucket, String object, Long generation, String e Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_DELETE_OBJECT_ACL); Scope scope = tracer.withSpan(span); try { - storage - .objectAccessControls() - .delete(bucket, object, entity) - .setGeneration(generation) - .execute(); + ObjectAccessControls.Delete delete = + storage.objectAccessControls().delete(bucket, object, entity).setGeneration(generation); + delete.execute(); return true; } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); @@ -1521,11 +1572,12 @@ public ObjectAccessControl createAcl(ObjectAccessControl acl) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_OBJECT_ACL); Scope scope = tracer.withSpan(span); try { - return storage - .objectAccessControls() - .insert(acl.getBucket(), acl.getObject(), acl) - .setGeneration(acl.getGeneration()) - .execute(); + ObjectAccessControls.Insert insert = + storage + .objectAccessControls() + .insert(acl.getBucket(), acl.getObject(), acl) + .setGeneration(acl.getGeneration()); + return insert.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -1540,11 +1592,12 @@ public ObjectAccessControl patchAcl(ObjectAccessControl acl) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_PATCH_OBJECT_ACL); Scope scope = tracer.withSpan(span); try { - return storage - .objectAccessControls() - .patch(acl.getBucket(), acl.getObject(), acl.getEntity(), acl) - .setGeneration(acl.getGeneration()) - .execute(); + ObjectAccessControls.Patch patch = + storage + .objectAccessControls() + .patch(acl.getBucket(), acl.getObject(), acl.getEntity(), acl) + .setGeneration(acl.getGeneration()); + return patch.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -1559,12 +1612,9 @@ public List listAcls(String bucket, String object, Long gen Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_LIST_OBJECT_ACLS); Scope scope = tracer.withSpan(span); try { - return storage - .objectAccessControls() - .list(bucket, object) - .setGeneration(generation) - .execute() - .getItems(); + ObjectAccessControls.List list = + storage.objectAccessControls().list(bucket, object).setGeneration(generation); + return list.execute().getItems(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -1583,13 +1633,14 @@ public HmacKey createHmacKey(String serviceAccountEmail, Map options) projectId = this.options.getProjectId(); } try { - return storage - .projects() - .hmacKeys() - .create(projectId, serviceAccountEmail) - .setUserProject(Option.USER_PROJECT.getString(options)) - .setDisableGZipContent(true) - .execute(); + Create create = + storage + .projects() + .hmacKeys() + .create(projectId, serviceAccountEmail) + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(create, options); + return create.setDisableGZipContent(true).execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -1608,7 +1659,7 @@ public Tuple> listHmacKeys(Map opti projectId = this.options.getProjectId(); } try { - HmacKeysMetadata hmacKeysMetadata = + HmacKeys.List list = storage .projects() .hmacKeys() @@ -1617,8 +1668,9 @@ public Tuple> listHmacKeys(Map opti .setPageToken(Option.PAGE_TOKEN.getString(options)) .setMaxResults(Option.MAX_RESULTS.getLong(options)) .setShowDeletedKeys(Option.SHOW_DELETED_KEYS.getBoolean(options)) - .setUserProject(Option.USER_PROJECT.getString(options)) - .execute(); + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(list, options); + HmacKeysMetadata hmacKeysMetadata = list.execute(); return Tuple.>of( hmacKeysMetadata.getNextPageToken(), hmacKeysMetadata.getItems()); } catch (IOException ex) { @@ -1639,12 +1691,14 @@ public HmacKeyMetadata getHmacKey(String accessId, Map options) { projectId = this.options.getProjectId(); } try { - return storage - .projects() - .hmacKeys() - .get(projectId, accessId) - .setUserProject(Option.USER_PROJECT.getString(options)) - .execute(); + HmacKeys.Get get = + storage + .projects() + .hmacKeys() + .get(projectId, accessId) + .setUserProject(Option.USER_PROJECT.getString(options)); + setExtraHeaders(get, options); + return get.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -1663,12 +1717,14 @@ public HmacKeyMetadata updateHmacKey(HmacKeyMetadata hmacKeyMetadata, Map