From ecd5162dec640008bb51ef4c7e66935c24a893fa Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:43:33 -0400 Subject: [PATCH 01/14] chore(main): release 2.59.1-SNAPSHOT (#3357) 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 d391400bd1..eb10795fd7 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.59.0 + 2.59.1-SNAPSHOT gapic-google-cloud-storage-v2 GRPC library for gapic-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.59.0 + 2.59.1-SNAPSHOT diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml index 4d5e1276d2..f11503a51c 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.59.0 + 2.59.1-SNAPSHOT pom com.google.cloud @@ -69,37 +69,37 @@ com.google.cloud google-cloud-storage - 2.59.0 + 2.59.1-SNAPSHOT com.google.api.grpc gapic-google-cloud-storage-v2 - 2.59.0 + 2.59.1-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-v2 - 2.59.0 + 2.59.1-SNAPSHOT com.google.api.grpc proto-google-cloud-storage-v2 - 2.59.0 + 2.59.1-SNAPSHOT com.google.cloud google-cloud-storage-control - 2.59.0 + 2.59.1-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.59.0 + 2.59.1-SNAPSHOT com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.59.0 + 2.59.1-SNAPSHOT diff --git a/google-cloud-storage-control/pom.xml b/google-cloud-storage-control/pom.xml index 932f08ab6a..d85264ab04 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.59.0 + 2.59.1-SNAPSHOT google-cloud-storage-control GRPC library for google-cloud-storage-control com.google.cloud google-cloud-storage-parent - 2.59.0 + 2.59.1-SNAPSHOT diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index bfa8010af4..718170e533 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -2,7 +2,7 @@ 4.0.0 google-cloud-storage - 2.59.0 + 2.59.1-SNAPSHOT jar Google Cloud Storage https://github.com/googleapis/java-storage @@ -12,7 +12,7 @@ com.google.cloud google-cloud-storage-parent - 2.59.0 + 2.59.1-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 9d25ee46c5..3e6ac29825 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.59.0 + 2.59.1-SNAPSHOT grpc-google-cloud-storage-control-v2 GRPC library for google-cloud-storage com.google.cloud google-cloud-storage-parent - 2.59.0 + 2.59.1-SNAPSHOT diff --git a/grpc-google-cloud-storage-v2/pom.xml b/grpc-google-cloud-storage-v2/pom.xml index 9d5fb64685..8dd4c9e1e2 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.59.0 + 2.59.1-SNAPSHOT grpc-google-cloud-storage-v2 GRPC library for grpc-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.59.0 + 2.59.1-SNAPSHOT diff --git a/pom.xml b/pom.xml index a9ef213685..0d423e33b0 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-storage-parent pom - 2.59.0 + 2.59.1-SNAPSHOT Storage Parent https://github.com/googleapis/java-storage @@ -82,7 +82,7 @@ com.google.cloud google-cloud-storage - 2.59.0 + 2.59.1-SNAPSHOT com.google.apis @@ -104,32 +104,32 @@ com.google.api.grpc proto-google-cloud-storage-v2 - 2.59.0 + 2.59.1-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-v2 - 2.59.0 + 2.59.1-SNAPSHOT com.google.api.grpc gapic-google-cloud-storage-v2 - 2.59.0 + 2.59.1-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.59.0 + 2.59.1-SNAPSHOT com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.59.0 + 2.59.1-SNAPSHOT com.google.cloud google-cloud-storage-control - 2.59.0 + 2.59.1-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 a258e6b249..edf1e6e394 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.59.0 + 2.59.1-SNAPSHOT proto-google-cloud-storage-control-v2 Proto library for proto-google-cloud-storage-control-v2 com.google.cloud google-cloud-storage-parent - 2.59.0 + 2.59.1-SNAPSHOT diff --git a/proto-google-cloud-storage-v2/pom.xml b/proto-google-cloud-storage-v2/pom.xml index 7788c504e4..44c2c2eb35 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.59.0 + 2.59.1-SNAPSHOT proto-google-cloud-storage-v2 PROTO library for proto-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.59.0 + 2.59.1-SNAPSHOT diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 2ac6fe76f2..0c5d92eed0 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,12 +28,12 @@ com.google.cloud google-cloud-storage - 2.59.0 + 2.59.1-SNAPSHOT com.google.cloud google-cloud-storage-control - 2.59.0 + 2.59.1-SNAPSHOT compile @@ -70,7 +70,7 @@ com.google.cloud google-cloud-storage - 2.59.0 + 2.59.1-SNAPSHOT tests test diff --git a/storage-shared-benchmarking/pom.xml b/storage-shared-benchmarking/pom.xml index 2379fec2f2..ffd421ac14 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.59.0 + 2.59.1-SNAPSHOT @@ -31,7 +31,7 @@ com.google.cloud google-cloud-storage - 2.59.0 + 2.59.1-SNAPSHOT tests diff --git a/versions.txt b/versions.txt index e73ee03edb..47e29cbca1 100644 --- a/versions.txt +++ b/versions.txt @@ -1,10 +1,10 @@ # Format: # module:released-version:current-version -google-cloud-storage:2.59.0:2.59.0 -gapic-google-cloud-storage-v2:2.59.0:2.59.0 -grpc-google-cloud-storage-v2:2.59.0:2.59.0 -proto-google-cloud-storage-v2:2.59.0:2.59.0 -google-cloud-storage-control:2.59.0:2.59.0 -proto-google-cloud-storage-control-v2:2.59.0:2.59.0 -grpc-google-cloud-storage-control-v2:2.59.0:2.59.0 +google-cloud-storage:2.59.0:2.59.1-SNAPSHOT +gapic-google-cloud-storage-v2:2.59.0:2.59.1-SNAPSHOT +grpc-google-cloud-storage-v2:2.59.0:2.59.1-SNAPSHOT +proto-google-cloud-storage-v2:2.59.0:2.59.1-SNAPSHOT +google-cloud-storage-control:2.59.0:2.59.1-SNAPSHOT +proto-google-cloud-storage-control-v2:2.59.0:2.59.1-SNAPSHOT +grpc-google-cloud-storage-control-v2:2.59.0:2.59.1-SNAPSHOT From e56e45a658fcd55b10635df4ebc776ed6689de8b Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 21 Oct 2025 21:13:50 +0100 Subject: [PATCH 02/14] chore(deps): update storage release dependencies to v2.59.0 (#3358) --- 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 b77249bb47..83c0d078c8 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.58.1 + 2.59.0 com.google.cloud google-cloud-storage-control - 2.58.1 + 2.59.0 @@ -78,7 +78,7 @@ com.google.cloud google-cloud-storage - 2.58.1 + 2.59.0 tests test diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 36544329ce..035e1b3cdd 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -99,7 +99,7 @@ com.google.cloud google-cloud-storage - 2.58.1 + 2.59.0 tests test From c1a8968799c1cf5a970fe9f303adccdad0a117c8 Mon Sep 17 00:00:00 2001 From: cloud-java-bot <122572305+cloud-java-bot@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:53:36 -0400 Subject: [PATCH 03/14] chore: Update generation configuration at Wed Oct 22 02:29:18 UTC 2025 (#3342) --- .github/workflows/hermetic_library_generation.yaml | 2 +- .kokoro/presubmit/graalvm-native-a.cfg | 2 +- .kokoro/presubmit/graalvm-native-b.cfg | 2 +- .kokoro/presubmit/graalvm-native-c.cfg | 2 +- README.md | 6 +++--- generation_config.yaml | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/hermetic_library_generation.yaml b/.github/workflows/hermetic_library_generation.yaml index a2011e0037..9872c68489 100644 --- a/.github/workflows/hermetic_library_generation.yaml +++ b/.github/workflows/hermetic_library_generation.yaml @@ -43,7 +43,7 @@ jobs: with: fetch-depth: 0 token: ${{ secrets.CLOUD_JAVA_BOT_TOKEN }} - - uses: googleapis/sdk-platform-java/.github/scripts@v2.62.3 + - uses: googleapis/sdk-platform-java/.github/scripts@v2.63.0 if: env.SHOULD_RUN == 'true' with: base_ref: ${{ github.base_ref }} diff --git a/.kokoro/presubmit/graalvm-native-a.cfg b/.kokoro/presubmit/graalvm-native-a.cfg index b7567eeb7d..55c5543e26 100644 --- a/.kokoro/presubmit/graalvm-native-a.cfg +++ b/.kokoro/presubmit/graalvm-native-a.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.52.3" # {x-version-update:google-cloud-shared-dependencies:current} + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.53.0" # {x-version-update:google-cloud-shared-dependencies:current} } env_vars: { diff --git a/.kokoro/presubmit/graalvm-native-b.cfg b/.kokoro/presubmit/graalvm-native-b.cfg index c7205f0abd..5c981b9848 100644 --- a/.kokoro/presubmit/graalvm-native-b.cfg +++ b/.kokoro/presubmit/graalvm-native-b.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.52.3" # {x-version-update:google-cloud-shared-dependencies:current} + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.53.0" # {x-version-update:google-cloud-shared-dependencies:current} } env_vars: { diff --git a/.kokoro/presubmit/graalvm-native-c.cfg b/.kokoro/presubmit/graalvm-native-c.cfg index f6ab8976a5..f2032499df 100644 --- a/.kokoro/presubmit/graalvm-native-c.cfg +++ b/.kokoro/presubmit/graalvm-native-c.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_c:3.52.3" # {x-version-update:google-cloud-shared-dependencies:current} + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_c:3.53.0" # {x-version-update:google-cloud-shared-dependencies:current} } env_vars: { diff --git a/README.md b/README.md index 78d10b82bd..eb5054d7d4 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.58.1 + 2.59.0 com.google.cloud google-cloud-storage-control - 2.58.1 + 2.59.0 ``` @@ -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.69.0') +implementation platform('com.google.cloud:libraries-bom:26.70.0') implementation 'com.google.cloud:google-cloud-storage' ``` diff --git a/generation_config.yaml b/generation_config.yaml index f9ad815233..e5d15d30a6 100644 --- a/generation_config.yaml +++ b/generation_config.yaml @@ -1,6 +1,6 @@ -gapic_generator_version: 2.62.3 -googleapis_commitish: 7b2b58ff4fb3eee3c0923af35fdee90134fabe3b -libraries_bom_version: 26.69.0 +gapic_generator_version: 2.63.0 +googleapis_commitish: 94ccdfe4519e0ba817bd33aa22eb9c64f88a6874 +libraries_bom_version: 26.70.0 libraries: - api_shortname: storage name_pretty: Cloud Storage From f0787cc213d2a15b6c8769a5d2030b1f60f7a45e Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 22 Oct 2025 20:35:25 +0100 Subject: [PATCH 04/14] build(deps): update dependency org.codehaus.mojo:exec-maven-plugin to v3.6.2 (#3353) --- google-cloud-storage/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 718170e533..91ce32083d 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -487,7 +487,7 @@ org.codehaus.mojo exec-maven-plugin - 3.6.1 + 3.6.2 From 763be91bd8532b4fc8c3a3c2248509c0c5256086 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 22 Oct 2025 20:58:27 +0100 Subject: [PATCH 05/14] test(deps): update cross product test dependencies (#3355) --- google-cloud-storage/pom.xml | 6 +++--- pom.xml | 2 +- samples/install-without-bom/pom.xml | 4 ++-- samples/snapshot/pom.xml | 4 ++-- samples/snippets/pom.xml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 91ce32083d..a83bc576fc 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -16,7 +16,7 @@ google-cloud-storage - 1.124.0 + 1.125.0 @@ -239,14 +239,14 @@ com.google.api.grpc proto-google-cloud-kms-v1 - 0.171.0 + 0.172.0 test com.google.cloud google-cloud-kms - 2.80.0 + 2.81.0 test diff --git a/pom.xml b/pom.xml index 0d423e33b0..593f316fd7 100644 --- a/pom.xml +++ b/pom.xml @@ -92,7 +92,7 @@ com.google.cloud google-cloud-pubsub - 1.142.0 + 1.143.0 test diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 83c0d078c8..1da04a0133 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -66,13 +66,13 @@ com.google.cloud google-cloud-pubsub - 1.142.0 + 1.143.0 test com.google.cloud google-cloud-kms - 2.80.0 + 2.81.0 test diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 0c5d92eed0..1b8a94203f 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -58,13 +58,13 @@ com.google.cloud google-cloud-pubsub - 1.142.0 + 1.143.0 test com.google.cloud google-cloud-kms - 2.80.0 + 2.81.0 test diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 035e1b3cdd..bfa3500c1a 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -76,13 +76,13 @@ com.google.cloud google-cloud-pubsub - 1.142.0 + 1.143.0 test com.google.cloud google-cloud-kms - 2.80.0 + 2.81.0 test From ebf5e6d30d8dc197ab388a70cc0d465c0f740496 Mon Sep 17 00:00:00 2001 From: Luwei Ge Date: Tue, 28 Oct 2025 16:20:07 -0700 Subject: [PATCH 06/14] fix: add new system property (com.google.cloud.storage.grpc.bound_token) to allow disabling bound token use with grpc (#3365) Co-authored-by: BenWhitehead --- .../google/cloud/storage/GrpcStorageOptions.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) 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 54dd12519d..1a6726b9c0 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 @@ -134,6 +134,10 @@ public final class GrpcStorageOptions extends StorageOptions private static final String GCS_SCOPE = "https://www.googleapis.com/auth/devstorage.full_control"; private static final Set SCOPES = ImmutableSet.of(GCS_SCOPE); private static final String DEFAULT_HOST = "https://storage.googleapis.com"; + // If true, disable the bound-token-by-default feature for DirectPath. + private static final boolean DIRECT_PATH_BOUND_TOKEN_DISABLED = + Boolean.parseBoolean( + System.getProperty("com.google.cloud.storage.grpc.bound_token", "false")); private final GrpcRetryAlgorithmManager retryAlgorithmManager; private final java.time.Duration terminationAwaitDuration; @@ -318,10 +322,12 @@ private Tuple> resolveSettingsAndOpts() throw InstantiatingGrpcChannelProvider.newBuilder() .setEndpoint(endpoint) .setAllowNonDefaultServiceAccount(true) - .setAttemptDirectPath(attemptDirectPath) - .setAllowHardBoundTokenTypes( - Collections.singletonList( - InstantiatingGrpcChannelProvider.HardBoundTokenTypes.ALTS)); + .setAttemptDirectPath(attemptDirectPath); + + if (!DIRECT_PATH_BOUND_TOKEN_DISABLED) { + channelProviderBuilder.setAllowHardBoundTokenTypes( + Collections.singletonList(InstantiatingGrpcChannelProvider.HardBoundTokenTypes.ALTS)); + } if (!NoopGrpcInterceptorProvider.INSTANCE.equals(grpcInterceptorProvider)) { channelProviderBuilder.setInterceptorProvider(grpcInterceptorProvider); From cfc92f7fd53735b7612e33e7bb405d2e038e89c9 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 28 Oct 2025 23:22:00 +0000 Subject: [PATCH 07/14] test(deps): update gcr.io/cloud-devrel-public-resources/storage-testbench docker tag to v0.56.0 (#3367) --- .../com/google/cloud/storage/it/runner/registry/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-storage/src/test/resources/com/google/cloud/storage/it/runner/registry/Dockerfile b/google-cloud-storage/src/test/resources/com/google/cloud/storage/it/runner/registry/Dockerfile index 8d979f05ee..6ba8de349c 100644 --- a/google-cloud-storage/src/test/resources/com/google/cloud/storage/it/runner/registry/Dockerfile +++ b/google-cloud-storage/src/test/resources/com/google/cloud/storage/it/runner/registry/Dockerfile @@ -1 +1 @@ -FROM gcr.io/cloud-devrel-public-resources/storage-testbench:v0.55.0 +FROM gcr.io/cloud-devrel-public-resources/storage-testbench:v0.56.0 From 03e626140ee2d43b8433838ff4b54ecc1b4c6bab Mon Sep 17 00:00:00 2001 From: cloud-java-bot <122572305+cloud-java-bot@users.noreply.github.com> Date: Wed, 29 Oct 2025 12:15:33 -0400 Subject: [PATCH 08/14] chore: Update generation configuration at Wed Oct 29 02:30:41 UTC 2025 (#3362) --- README.md | 2 +- generation_config.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eb5054d7d4..4fa6de6833 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.70.0') +implementation platform('com.google.cloud:libraries-bom:26.71.0') implementation 'com.google.cloud:google-cloud-storage' ``` diff --git a/generation_config.yaml b/generation_config.yaml index e5d15d30a6..80b7630256 100644 --- a/generation_config.yaml +++ b/generation_config.yaml @@ -1,6 +1,6 @@ gapic_generator_version: 2.63.0 -googleapis_commitish: 94ccdfe4519e0ba817bd33aa22eb9c64f88a6874 -libraries_bom_version: 26.70.0 +googleapis_commitish: 72e7439c8e7e9986cf1865e337fc7c64ca5bda1f +libraries_bom_version: 26.71.0 libraries: - api_shortname: storage name_pretty: Cloud Storage From 87c9c8125cb97230c52e465012b5069d31744a79 Mon Sep 17 00:00:00 2001 From: Nidhi Date: Fri, 31 Oct 2025 03:38:53 +0530 Subject: [PATCH 09/14] fix: Ignore tests failing due to public access prevention policy (#3374) --- .../src/test/java/com/example/storage/ITBucketSnippets.java | 2 ++ .../src/test/java/com/example/storage/ITObjectSnippets.java | 3 +++ 2 files changed, 5 insertions(+) diff --git a/samples/snippets/src/test/java/com/example/storage/ITBucketSnippets.java b/samples/snippets/src/test/java/com/example/storage/ITBucketSnippets.java index 051bd05e23..6d649ef965 100644 --- a/samples/snippets/src/test/java/com/example/storage/ITBucketSnippets.java +++ b/samples/snippets/src/test/java/com/example/storage/ITBucketSnippets.java @@ -101,6 +101,7 @@ import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -425,6 +426,7 @@ public void testAddListRemoveBucketIamMembers() throws Throwable { .build()); } + @Ignore("TODO(b/456381873): Test fails in CI due to project's public access prevention policy.") @Test public void testMakeBucketPublic() throws Throwable { MakeBucketPublic.makeBucketPublic(PROJECT_ID, BUCKET); diff --git a/samples/snippets/src/test/java/com/example/storage/ITObjectSnippets.java b/samples/snippets/src/test/java/com/example/storage/ITObjectSnippets.java index 28ceeb3081..c55b2041a1 100644 --- a/samples/snippets/src/test/java/com/example/storage/ITObjectSnippets.java +++ b/samples/snippets/src/test/java/com/example/storage/ITObjectSnippets.java @@ -92,6 +92,7 @@ import java.util.Random; import javax.net.ssl.HttpsURLConnection; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -167,6 +168,7 @@ public void testDownloadObjectIntoMemory() throws IOException { assertThat(snippetOutput).contains("The contents of " + objectName); } + @Ignore("TODO(b/456381873): Test fails in CI due to project's public access prevention policy.") @Test public void testDownloadPublicObject() throws Exception { try (TemporaryBucket tmpBucket = @@ -436,6 +438,7 @@ public void testV4SignedURLs() throws IOException { } } + @Ignore("TODO(b/456381873): Test fails in CI due to project's public access prevention policy.") @Test public void testMakeObjectPublic() { String aclBlob = generator.randomObjectName(); From 160fa9af7aa492373a9d9b40f65a6c56d7cab5ef Mon Sep 17 00:00:00 2001 From: Shreyas Sinha Date: Mon, 3 Nov 2025 21:06:41 +0000 Subject: [PATCH 10/14] chore: mpu client preview merge train BEGIN_NESTED_COMMIT BEGIN_COMMIT_OVERRIDE feat: add preview MultipartUploadClient#createMultipartUpload #3356 END_COMMIT_OVERRIDE BEGIN_COMMIT_OVERRIDE feat: add preview MultipartUploadClient#listParts #3359 END_COMMIT_OVERRIDE BEGIN_COMMIT_OVERRIDE feat: add preview MultipartUploadClient#abortMultipartUpload #3361 END_COMMIT_OVERRIDE BEGIN_COMMIT_OVERRIDE feat: add preview MultipartUploadClient#uploadPart #3375 END_COMMIT_OVERRIDE BEGIN_COMMIT_OVERRIDE feat: add preview MultipartUploadClient#completeMultipartUpload #3372 END_COMMIT_OVERRIDE BEGIN_COMMIT_OVERRIDE feat: add preview MultipartUploadSettings END_COMMIT_OVERRIDE BEGIN_COMMIT_OVERRIDE END_COMMIT_OVERRIDE END_NESTED_COMMIT Other changes: 1. chore: refactor retrier creation from HttpStorageOptions to StorageOptions #3350 2. chore: refactorings for CreateMultipartUpload #3364 3. chore: fix xml parsing of StringEnumValue's so that the enum contract is not broken #3377 4. chore: add PredefinedAcl#xmlEntry Co-authored-by: BenWhitehead --- google-cloud-storage/pom.xml | 16 + .../cloud/storage/ChecksumResponseParser.java | 65 ++ .../storage/HttpRetryAlgorithmManager.java | 4 + .../cloud/storage/HttpStorageOptions.java | 9 +- .../cloud/storage/MultipartUploadClient.java | 119 +++ .../storage/MultipartUploadClientImpl.java | 103 +++ .../MultipartUploadHttpRequestManager.java | 276 ++++++ .../storage/MultipartUploadSettings.java | 54 ++ .../com/google/cloud/storage/RequestBody.java | 70 ++ .../cloud/storage/RewindableContent.java | 60 ++ .../com/google/cloud/storage/Storage.java | 24 +- .../google/cloud/storage/StorageOptions.java | 9 + .../java/com/google/cloud/storage/Utils.java | 5 + .../google/cloud/storage/XmlObjectParser.java | 143 +++ .../model/AbortMultipartUploadRequest.java | 145 +++ .../model/AbortMultipartUploadResponse.java | 27 + .../model/CompleteMultipartUploadRequest.java | 205 +++++ .../CompleteMultipartUploadResponse.java | 247 +++++ .../model/CompletedMultipartUpload.java | 123 +++ .../multipartupload/model/CompletedPart.java | 120 +++ .../model/CreateMultipartUploadRequest.java | 399 +++++++++ .../model/CreateMultipartUploadResponse.java | 188 ++++ .../model/ListPartsRequest.java | 237 +++++ .../model/ListPartsResponse.java | 385 ++++++++ .../multipartupload/model/ObjectLockMode.java | 91 ++ .../storage/multipartupload/model/Part.java | 217 +++++ .../model/UploadPartRequest.java | 206 +++++ .../model/UploadPartResponse.java | 144 +++ .../storage/ChecksumResponseParserTest.java | 102 +++ ...MultipartUploadHttpRequestManagerTest.java | 843 ++++++++++++++++++ .../cloud/storage/XmlObjectParserTest.java | 181 ++++ .../it/ITMultipartUploadClientTest.java | 418 +++++++++ 32 files changed, 5218 insertions(+), 17 deletions(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/ChecksumResponseParser.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClientImpl.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadSettings.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/RequestBody.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/XmlObjectParser.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/AbortMultipartUploadRequest.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/AbortMultipartUploadResponse.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompleteMultipartUploadRequest.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompleteMultipartUploadResponse.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompletedMultipartUpload.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompletedPart.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CreateMultipartUploadRequest.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CreateMultipartUploadResponse.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListPartsRequest.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListPartsResponse.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ObjectLockMode.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/Part.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/UploadPartRequest.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/UploadPartResponse.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/ChecksumResponseParserTest.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/ITMultipartUploadHttpRequestManagerTest.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/XmlObjectParserTest.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITMultipartUploadClientTest.java diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index a83bc576fc..54492aff0b 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -19,6 +19,22 @@ 1.125.0 + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-annotations + com.google.guava guava diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ChecksumResponseParser.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ChecksumResponseParser.java new file mode 100644 index 0000000000..4d32cb42e8 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ChecksumResponseParser.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; + +import com.google.api.client.http.HttpResponse; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.UploadPartResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** A utility class to parse {@link HttpResponse} and create a {@link UploadPartResponse}. */ +final class ChecksumResponseParser { + + private ChecksumResponseParser() {} + + static UploadPartResponse parseUploadResponse(HttpResponse response) { + String eTag = response.getHeaders().getETag(); + Map hashes = extractHashesFromHeader(response); + return UploadPartResponse.builder().eTag(eTag).md5(hashes.get("md5")).build(); + } + + static CompleteMultipartUploadResponse parseCompleteResponse(HttpResponse response) + throws IOException { + Map hashes = extractHashesFromHeader(response); + CompleteMultipartUploadResponse completeMpuResponse = + response.parseAs(CompleteMultipartUploadResponse.class); + return CompleteMultipartUploadResponse.builder() + .location(completeMpuResponse.location()) + .bucket(completeMpuResponse.bucket()) + .key(completeMpuResponse.key()) + .etag(completeMpuResponse.etag()) + .crc32c(hashes.get("crc32c")) + .build(); + } + + static Map extractHashesFromHeader(HttpResponse response) { + return Optional.ofNullable(response.getHeaders().getFirstHeaderStringValue("x-goog-hash")) + .map( + h -> + Arrays.stream(h.split(",")) + .map(s -> s.trim().split("=", 2)) + .filter(a -> a.length == 2) + .filter(a -> "crc32c".equalsIgnoreCase(a[0]) || "md5".equalsIgnoreCase(a[0])) + .collect(Collectors.toMap(a -> a[0].toLowerCase(), a -> a[1], (v1, v2) -> v1))) + .orElse(Collections.emptyMap()); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpRetryAlgorithmManager.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpRetryAlgorithmManager.java index a2564860c2..3db42e3ac3 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpRetryAlgorithmManager.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpRetryAlgorithmManager.java @@ -50,6 +50,10 @@ ResultRetryAlgorithm idempotent() { return retryStrategy.getIdempotentHandler(); } + ResultRetryAlgorithm nonIdempotent() { + return retryStrategy.getNonidempotentHandler(); + } + public ResultRetryAlgorithm getForBucketAclCreate( BucketAccessControl pb, Map optionsMap) { return retryStrategy.getNonidempotentHandler(); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpStorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpStorageOptions.java index b1400bcb62..dac8a010cd 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpStorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpStorageOptions.java @@ -32,7 +32,6 @@ import com.google.cloud.http.HttpTransportOptions; import com.google.cloud.spi.ServiceRpcFactory; import com.google.cloud.storage.BlobWriteSessionConfig.WriterFactory; -import com.google.cloud.storage.Retrying.DefaultRetrier; import com.google.cloud.storage.Retrying.HttpRetrier; import com.google.cloud.storage.Retrying.RetryingDependencies; import com.google.cloud.storage.Storage.BlobWriteOption; @@ -409,13 +408,7 @@ public Storage create(StorageOptions options) { WriterFactory factory = blobWriteSessionConfig.createFactory(clock); StorageImpl storage = new StorageImpl( - httpStorageOptions, - factory, - new HttpRetrier( - new DefaultRetrier( - OtelStorageDecorator.retryContextDecorator(otel), - RetryingDependencies.simple( - options.getClock(), options.getRetrySettings())))); + httpStorageOptions, factory, new HttpRetrier(options.createRetrier())); return OtelStorageDecorator.decorate(storage, otel, Transport.HTTP); } catch (IOException e) { throw new IllegalStateException( diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java new file mode 100644 index 0000000000..09a3af01c5 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java @@ -0,0 +1,119 @@ +/* + * 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; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalExtensionOnly; +import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.ListPartsRequest; +import com.google.cloud.storage.multipartupload.model.ListPartsResponse; +import com.google.cloud.storage.multipartupload.model.UploadPartRequest; +import com.google.cloud.storage.multipartupload.model.UploadPartResponse; +import java.net.URI; + +/** + * A client for interacting with Google Cloud Storage's Multipart Upload API. + * + *

This class is for internal use only and is not intended for public consumption. It provides a + * low-level interface for creating and managing multipart uploads. + * + * @see Multipart Uploads + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +@InternalExtensionOnly +public abstract class MultipartUploadClient { + + MultipartUploadClient() {} + + /** + * Creates a new multipart upload. + * + * @param request The request object containing the details for creating the multipart upload. + * @return A {@link CreateMultipartUploadResponse} object containing the upload ID. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public abstract CreateMultipartUploadResponse createMultipartUpload( + CreateMultipartUploadRequest request); + + /** + * Lists the parts that have been uploaded for a specific multipart upload. + * + * @param listPartsRequest The request object containing the details for listing the parts. + * @return A {@link ListPartsResponse} object containing the list of parts. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public abstract ListPartsResponse listParts(ListPartsRequest listPartsRequest); + + /** + * Aborts a multipart upload. + * + * @param request The request object containing the details for aborting the multipart upload. + * @return An {@link AbortMultipartUploadResponse} object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public abstract AbortMultipartUploadResponse abortMultipartUpload( + AbortMultipartUploadRequest request); + + /** + * Completes a multipart upload. + * + * @param request The request object containing the details for completing the multipart upload. + * @return A {@link CompleteMultipartUploadResponse} object containing information about the + * completed upload. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public abstract CompleteMultipartUploadResponse completeMultipartUpload( + CompleteMultipartUploadRequest request); + + /** + * Uploads a part in a multipart upload. + * + * @param request The request object containing the details for uploading the part. + * @param requestBody The content of the part to upload. + * @return An {@link UploadPartResponse} object containing the ETag of the uploaded part. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public abstract UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requestBody); + + /** + * Creates a new instance of {@link MultipartUploadClient}. + * + * @param config The configuration for the client. + * @return A new {@link MultipartUploadClient} instance. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static MultipartUploadClient create(MultipartUploadSettings config) { + HttpStorageOptions options = config.getOptions(); + return new MultipartUploadClientImpl( + URI.create(options.getHost()), + options.createRetrier(), + MultipartUploadHttpRequestManager.createFrom(options), + options.getRetryAlgorithmManager()); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClientImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClientImpl.java new file mode 100644 index 0000000000..00b43c6ba9 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClientImpl.java @@ -0,0 +1,103 @@ +/* + * 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; + +import com.google.cloud.storage.Conversions.Decoder; +import com.google.cloud.storage.Retrying.Retrier; +import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.ListPartsRequest; +import com.google.cloud.storage.multipartupload.model.ListPartsResponse; +import com.google.cloud.storage.multipartupload.model.UploadPartRequest; +import com.google.cloud.storage.multipartupload.model.UploadPartResponse; +import java.net.URI; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This class is an implementation of {@link MultipartUploadClient} that uses the Google Cloud + * Storage XML API to perform multipart uploads. + */ +final class MultipartUploadClientImpl extends MultipartUploadClient { + + private final MultipartUploadHttpRequestManager httpRequestManager; + private final Retrier retrier; + private final URI uri; + private final HttpRetryAlgorithmManager retryAlgorithmManager; + + MultipartUploadClientImpl( + URI uri, + Retrier retrier, + MultipartUploadHttpRequestManager multipartUploadHttpRequestManager, + HttpRetryAlgorithmManager retryAlgorithmManager) { + this.httpRequestManager = multipartUploadHttpRequestManager; + this.retrier = retrier; + this.uri = uri; + this.retryAlgorithmManager = retryAlgorithmManager; + } + + @Override + public CreateMultipartUploadResponse createMultipartUpload(CreateMultipartUploadRequest request) { + return retrier.run( + retryAlgorithmManager.nonIdempotent(), + () -> httpRequestManager.sendCreateMultipartUploadRequest(uri, request), + Decoder.identity()); + } + + @Override + public ListPartsResponse listParts(ListPartsRequest request) { + + return retrier.run( + retryAlgorithmManager.idempotent(), + () -> httpRequestManager.sendListPartsRequest(uri, request), + Decoder.identity()); + } + + @Override + public AbortMultipartUploadResponse abortMultipartUpload(AbortMultipartUploadRequest request) { + + return retrier.run( + retryAlgorithmManager.idempotent(), + () -> httpRequestManager.sendAbortMultipartUploadRequest(uri, request), + Decoder.identity()); + } + + @Override + public CompleteMultipartUploadResponse completeMultipartUpload( + CompleteMultipartUploadRequest request) { + return retrier.run( + retryAlgorithmManager.idempotent(), + () -> httpRequestManager.sendCompleteMultipartUploadRequest(uri, request), + Decoder.identity()); + } + + @Override + public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requestBody) { + AtomicBoolean dirty = new AtomicBoolean(false); + return retrier.run( + retryAlgorithmManager.idempotent(), + () -> { + if (dirty.getAndSet(true)) { + requestBody.getContent().rewindTo(0); + } + return httpRequestManager.sendUploadPartRequest(uri, request, requestBody.getContent()); + }, + Decoder.identity()); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java new file mode 100644 index 0000000000..be3a06730a --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java @@ -0,0 +1,276 @@ +/* + * 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; + +import static com.google.cloud.storage.Utils.ifNonNull; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.google.api.client.http.ByteArrayContent; +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.HttpRequestFactory; +import com.google.api.client.http.UriTemplate; +import com.google.api.client.util.ObjectParser; +import com.google.api.gax.core.GaxProperties; +import com.google.api.gax.rpc.FixedHeaderProvider; +import com.google.api.gax.rpc.HeaderProvider; +import com.google.api.services.storage.Storage; +import com.google.cloud.storage.Crc32cValue.Crc32cLengthKnown; +import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.ListPartsRequest; +import com.google.cloud.storage.multipartupload.model.ListPartsResponse; +import com.google.cloud.storage.multipartupload.model.UploadPartRequest; +import com.google.cloud.storage.multipartupload.model.UploadPartResponse; +import com.google.common.base.StandardSystemProperty; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.checkerframework.checker.nullness.qual.Nullable; + +final class MultipartUploadHttpRequestManager { + + private final HttpRequestFactory requestFactory; + private final ObjectParser objectParser; + private final HeaderProvider headerProvider; + + MultipartUploadHttpRequestManager( + HttpRequestFactory requestFactory, ObjectParser objectParser, HeaderProvider headerProvider) { + this.requestFactory = requestFactory; + this.objectParser = objectParser; + this.headerProvider = headerProvider; + } + + CreateMultipartUploadResponse sendCreateMultipartUploadRequest( + URI uri, CreateMultipartUploadRequest request) throws IOException { + + String createUri = + UriTemplate.expand( + uri.toString() + "{bucket}/{key}?uploads", + ImmutableMap.of("bucket", request.bucket(), "key", request.key()), + false); + + HttpRequest httpRequest = + requestFactory.buildPostRequest( + new GenericUrl(createUri), new ByteArrayContent(request.getContentType(), new byte[0])); + httpRequest.getHeaders().putAll(headerProvider.getHeaders()); + addHeadersForCreateMultipartUpload(request, httpRequest.getHeaders()); + httpRequest.setParser(objectParser); + httpRequest.setThrowExceptionOnExecuteError(true); + return httpRequest.execute().parseAs(CreateMultipartUploadResponse.class); + } + + ListPartsResponse sendListPartsRequest(URI uri, ListPartsRequest request) throws IOException { + + ImmutableMap.Builder params = + ImmutableMap.builder() + .put("bucket", request.bucket()) + .put("key", request.key()) + .put("uploadId", request.uploadId()); + if (request.getMaxParts() != null) { + params.put("max-parts", request.getMaxParts()); + } + if (request.getPartNumberMarker() != null) { + params.put("part-number-marker", request.getPartNumberMarker()); + } + + String listUri = + UriTemplate.expand( + uri.toString() + "{bucket}/{key}{?uploadId,max-parts,part-number-marker}", + params.build(), + false); + HttpRequest httpRequest = requestFactory.buildGetRequest(new GenericUrl(listUri)); + httpRequest.getHeaders().putAll(headerProvider.getHeaders()); + httpRequest.setParser(objectParser); + httpRequest.setThrowExceptionOnExecuteError(true); + return httpRequest.execute().parseAs(ListPartsResponse.class); + } + + AbortMultipartUploadResponse sendAbortMultipartUploadRequest( + URI uri, AbortMultipartUploadRequest request) throws IOException { + + String abortUri = + UriTemplate.expand( + uri.toString() + "{bucket}/{key}{?uploadId}", + ImmutableMap.of( + "bucket", request.bucket(), "key", request.key(), "uploadId", request.uploadId()), + false); + + HttpRequest httpRequest = requestFactory.buildDeleteRequest(new GenericUrl(abortUri)); + httpRequest.getHeaders().putAll(headerProvider.getHeaders()); + httpRequest.setParser(objectParser); + httpRequest.setThrowExceptionOnExecuteError(true); + return httpRequest.execute().parseAs(AbortMultipartUploadResponse.class); + } + + CompleteMultipartUploadResponse sendCompleteMultipartUploadRequest( + URI uri, CompleteMultipartUploadRequest request) throws IOException { + String completeUri = + UriTemplate.expand( + uri.toString() + "{bucket}/{key}{?uploadId}", + ImmutableMap.of( + "bucket", request.bucket(), "key", request.key(), "uploadId", request.uploadId()), + false); + byte[] bytes = new XmlMapper().writeValueAsBytes(request.multipartUpload()); + HttpRequest httpRequest = + requestFactory.buildPostRequest( + new GenericUrl(completeUri), new ByteArrayContent("application/xml", bytes)); + httpRequest.getHeaders().putAll(headerProvider.getHeaders()); + @Nullable Crc32cLengthKnown crc32cValue = Hasher.defaultHasher().hash(ByteBuffer.wrap(bytes)); + addChecksumHeader(crc32cValue, httpRequest.getHeaders()); + httpRequest.setParser(objectParser); + httpRequest.setThrowExceptionOnExecuteError(true); + return ChecksumResponseParser.parseCompleteResponse(httpRequest.execute()); + } + + UploadPartResponse sendUploadPartRequest( + URI uri, UploadPartRequest request, RewindableContent rewindableContent) throws IOException { + String uploadUri = + UriTemplate.expand( + uri.toString() + "{bucket}/{key}{?partNumber,uploadId}", + ImmutableMap.of( + "bucket", + request.bucket(), + "key", + request.key(), + "partNumber", + request.partNumber(), + "uploadId", + request.uploadId()), + false); + HttpRequest httpRequest = + requestFactory.buildPutRequest(new GenericUrl(uploadUri), rewindableContent); + httpRequest.getHeaders().putAll(headerProvider.getHeaders()); + addChecksumHeader(rewindableContent.getCrc32c(), httpRequest.getHeaders()); + httpRequest.setThrowExceptionOnExecuteError(true); + return ChecksumResponseParser.parseUploadResponse(httpRequest.execute()); + } + + @SuppressWarnings("DataFlowIssue") + static MultipartUploadHttpRequestManager createFrom(HttpStorageOptions options) { + Storage storage = options.getStorageRpcV1().getStorage(); + ImmutableMap.Builder stableHeaders = + ImmutableMap.builder() + // http-java-client will automatically append its own version to the user-agent + .put("User-Agent", "gcloud-java/" + options.getLibraryVersion()) + .put( + "x-goog-api-client", + String.format( + "gl-java/%s gccl/%s %s/%s", + GaxProperties.getJavaVersion(), + options.getLibraryVersion(), + formatName(StandardSystemProperty.OS_NAME.value()), + formatSemver(StandardSystemProperty.OS_VERSION.value()))); + ifNonNull(options.getProjectId(), pid -> stableHeaders.put("x-goog-user-project", pid)); + return new MultipartUploadHttpRequestManager( + storage.getRequestFactory(), + new XmlObjectParser(new XmlMapper()), + options.getMergedHeaderProvider(FixedHeaderProvider.create(stableHeaders.build()))); + } + + private void addChecksumHeader(@Nullable Crc32cLengthKnown crc32c, HttpHeaders headers) { + if (crc32c != null) { + headers.put("x-goog-hash", "crc32c=" + Utils.crc32cCodec.encode(crc32c.getValue())); + } + } + + private void addHeadersForCreateMultipartUpload( + CreateMultipartUploadRequest request, HttpHeaders headers) { + if (request.getCannedAcl() != null) { + headers.put("x-goog-acl", request.getCannedAcl().getXmlEntry()); + } + if (request.getMetadata() != null) { + for (Map.Entry entry : request.getMetadata().entrySet()) { + if (entry.getKey() != null || entry.getValue() != null) { + headers.put("x-goog-meta-" + urlEncode(entry.getKey()), urlEncode(entry.getValue())); + } + } + } + if (request.getContentType() != null) { + headers.put("Content-Type", request.getContentType()); + } + if (request.getStorageClass() != null) { + headers.put("x-goog-storage-class", request.getStorageClass().toString()); + } + if (request.getKmsKeyName() != null && !request.getKmsKeyName().isEmpty()) { + headers.put("x-goog-encryption-kms-key-name", request.getKmsKeyName()); + } + if (request.getObjectLockMode() != null) { + headers.put("x-goog-object-lock-mode", request.getObjectLockMode().toString()); + } + if (request.getObjectLockRetainUntilDate() != null) { + headers.put( + "x-goog-object-lock-retain-until-date", + Utils.offsetDateTimeRfc3339Codec.encode(request.getObjectLockRetainUntilDate())); + } + if (request.getCustomTime() != null) { + headers.put( + "x-goog-custom-time", Utils.offsetDateTimeRfc3339Codec.encode(request.getCustomTime())); + } + } + + private static String urlEncode(String value) { + try { + return URLEncoder.encode(value, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new StorageException(0, "Unable to load UTF-8 charset for encoding", e); + } + } + + /** + * copied from + * com.google.api.client.googleapis.services.AbstractGoogleClientRequest.ApiClientVersion#formatName(java.lang.String) + */ + private static String formatName(String name) { + // Only lowercase letters, digits, and "-" are allowed + return name.toLowerCase().replaceAll("[^\\w\\d\\-]", "-"); + } + + private static String formatSemver(String version) { + return formatSemver(version, version); + } + + /** + * copied from + * com.google.api.client.googleapis.services.AbstractGoogleClientRequest.ApiClientVersion#formatSemver(java.lang.String, + * java.lang.String) + */ + private static String formatSemver(String version, String defaultValue) { + if (version == null) { + return null; + } + + // Take only the semver version: x.y.z-a_b_c -> x.y.z + Matcher m = Pattern.compile("(\\d+\\.\\d+\\.\\d+).*").matcher(version); + if (m.find()) { + return m.group(1); + } else { + return defaultValue; + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadSettings.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadSettings.java new file mode 100644 index 0000000000..a4130929ab --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadSettings.java @@ -0,0 +1,54 @@ +/* + * 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; + +import com.google.api.core.BetaApi; + +/** + * Settings for configuring the {@link MultipartUploadClient}. + * + *

This class is for internal use only and is not intended for public consumption. + */ +@BetaApi +public final class MultipartUploadSettings { + private final HttpStorageOptions options; + + private MultipartUploadSettings(HttpStorageOptions options) { + this.options = options; + } + + /** + * Returns the {@link HttpStorageOptions} configured for multipart uploads. + * + * @return The {@link HttpStorageOptions}. + */ + @BetaApi + public HttpStorageOptions getOptions() { + return options; + } + + /** + * Creates a new {@code MultipartUploadSettings} instance with the specified {@link + * HttpStorageOptions}. + * + * @param options The {@link HttpStorageOptions} to use. + * @return A new {@code MultipartUploadSettings} instance. + */ + @BetaApi + public static MultipartUploadSettings of(HttpStorageOptions options) { + return new MultipartUploadSettings(options); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/RequestBody.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/RequestBody.java new file mode 100644 index 0000000000..a14cb173a5 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/RequestBody.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 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; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalExtensionOnly; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Path; + +/** + * The data of a single {@code UploadPart} in a GCS XML MPU. + * + *

Instances of this class are thread-safe and immutable. + * + * @see https://cloud.google.com/storage/docs/multipart-uploads#upload_parts + */ +@BetaApi +@InternalExtensionOnly +public final class RequestBody { + + private final RewindableContent content; + + private RequestBody(RewindableContent content) { + this.content = content; + } + + RewindableContent getContent() { + return content; + } + + /** Create a new empty RequestBody. */ + @BetaApi + public static RequestBody empty() { + return new RequestBody(RewindableContent.empty()); + } + + /** Create a new RequestBody from the given {@link ByteBuffer}s. */ + @BetaApi + public static RequestBody of(ByteBuffer... buffers) { + return new RequestBody(RewindableContent.of(buffers)); + } + + /** Create a new RequestBody from the given {@link ByteBuffer}s. */ + @BetaApi + public static RequestBody of(ByteBuffer[] srcs, int srcsOffset, int srcsLength) { + return new RequestBody(RewindableContent.of(srcs, srcsOffset, srcsLength)); + } + + /** Create a new RequestBody from the given {@link Path}. */ + @BetaApi + public static RequestBody of(Path path) throws IOException { + return new RequestBody(RewindableContent.of(path)); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableContent.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableContent.java index 8d299bfb54..e26d0cf558 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableContent.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableContent.java @@ -18,6 +18,9 @@ import com.google.api.client.http.AbstractHttpContent; import com.google.api.client.http.HttpMediaType; +import com.google.cloud.storage.Crc32cValue.Crc32cLengthKnown; +import com.google.cloud.storage.Hasher.GuavaHasher; +import com.google.cloud.storage.Hasher.NoOpHasher; import com.google.common.base.Preconditions; import com.google.common.io.ByteStreams; import java.io.IOException; @@ -32,6 +35,7 @@ import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.Locale; +import org.checkerframework.checker.nullness.qual.Nullable; abstract class RewindableContent extends AbstractHttpContent { @@ -55,6 +59,9 @@ public final boolean retrySupported() { return false; } + @Nullable + abstract Crc32cLengthKnown getCrc32c(); + static RewindableContent empty() { return EmptyRewindableContent.INSTANCE; } @@ -111,6 +118,11 @@ protected void rewindTo(long offset) {} @Override void flagDirty() {} + + @Override + @Nullable Crc32cLengthKnown getCrc32c() { + return Hasher.defaultHasher().initialValue(); + } } private static final class PathRewindableContent extends RewindableContent { @@ -165,6 +177,36 @@ long writeTo(GatheringByteChannel gbc) throws IOException { @Override void flagDirty() {} + + @Override + @Nullable Crc32cLengthKnown getCrc32c() { + GuavaHasher hasher; + { + Hasher defaultHasher = Hasher.defaultHasher(); + if (defaultHasher instanceof NoOpHasher) { + return null; + } else { + hasher = Hasher.enabled(); + } + } + Crc32cLengthKnown cumulative = Crc32cValue.zero(); + + int bufferSize = 8192; // 8KiB buffer for reading chunks + ByteBuffer buffer = ByteBuffer.allocate(bufferSize); + + try (SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ)) { + while (channel.read(buffer) != -1) { + buffer.flip(); + if (buffer.hasRemaining()) { + cumulative = cumulative.concat(hasher.hash(buffer::duplicate)); + } + buffer.clear(); + } + } catch (IOException e) { + throw new RuntimeException("Failed to read file for CRC32C calculation: " + path, e); + } + return cumulative; + } } private static final class ByteBufferContent extends RewindableContent { @@ -260,5 +302,23 @@ void rewindTo(long offset) { void flagDirty() { this.dirty = true; } + + @Override + @Nullable Crc32cLengthKnown getCrc32c() { + GuavaHasher hasher; + { + Hasher defaultHasher = Hasher.defaultHasher(); + if (defaultHasher instanceof NoOpHasher) { + return null; + } else { + hasher = Hasher.enabled(); + } + } + Crc32cLengthKnown cumulative = Crc32cValue.zero(); + for (ByteBuffer buffer : buffers) { + cumulative = cumulative.concat(hasher.hash(buffer::duplicate)); + } + return cumulative; + } } } 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 79e270875d..23e94e09f5 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 @@ -93,24 +93,30 @@ public interface Storage extends Service, AutoCloseable { @TransportCompatibility({Transport.HTTP, Transport.GRPC}) enum PredefinedAcl { - AUTHENTICATED_READ("authenticatedRead"), - ALL_AUTHENTICATED_USERS("allAuthenticatedUsers"), - PRIVATE("private"), - PROJECT_PRIVATE("projectPrivate"), - PUBLIC_READ("publicRead"), - PUBLIC_READ_WRITE("publicReadWrite"), - BUCKET_OWNER_READ("bucketOwnerRead"), - BUCKET_OWNER_FULL_CONTROL("bucketOwnerFullControl"); + AUTHENTICATED_READ("authenticatedRead", "authenticated-read"), + ALL_AUTHENTICATED_USERS("allAuthenticatedUsers", "all-authenticated-users"), + PRIVATE("private", "private"), + PROJECT_PRIVATE("projectPrivate", "project-private"), + PUBLIC_READ("publicRead", "public-read"), + PUBLIC_READ_WRITE("publicReadWrite", "public-read-write"), + BUCKET_OWNER_READ("bucketOwnerRead", "bucket-owner-read"), + BUCKET_OWNER_FULL_CONTROL("bucketOwnerFullControl", "bucket-owner-full-control"); private final String entry; + private final String xmlEntry; - PredefinedAcl(String entry) { + PredefinedAcl(String entry, String xmlEntry) { this.entry = entry; + this.xmlEntry = xmlEntry; } String getEntry() { return entry; } + + String getXmlEntry() { + return xmlEntry; + } } enum BucketField implements FieldSelector, NamedField { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java index 4dac2b43ef..723a11dc34 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java @@ -26,6 +26,9 @@ import com.google.cloud.storage.HttpStorageOptions.HttpStorageDefaults; import com.google.cloud.storage.HttpStorageOptions.HttpStorageFactory; import com.google.cloud.storage.HttpStorageOptions.HttpStorageRpcFactory; +import com.google.cloud.storage.Retrying.DefaultRetrier; +import com.google.cloud.storage.Retrying.Retrier; +import com.google.cloud.storage.Retrying.RetryingDependencies; import com.google.cloud.storage.Storage.BlobWriteOption; import com.google.cloud.storage.TransportCompatibility.Transport; import com.google.cloud.storage.spi.StorageRpcFactory; @@ -68,6 +71,12 @@ public abstract class StorageOptions extends ServiceOptions durationSecondsCodec = Codec.of(Duration::getSeconds, Duration::ofSeconds); + static final Codec offsetDateTimeRfc3339Codec = + Codec.of( + RFC_3339_DATE_TIME_FORMATTER::format, + s -> OffsetDateTime.parse(s, RFC_3339_DATE_TIME_FORMATTER)); + @VisibleForTesting static final Codec dateTimeCodec = Codec.of( diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/XmlObjectParser.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/XmlObjectParser.java new file mode 100644 index 0000000000..23577bf836 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/XmlObjectParser.java @@ -0,0 +1,143 @@ +/* + * 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; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleDeserializers; +import com.fasterxml.jackson.databind.module.SimpleSerializers; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.google.api.client.util.ObjectParser; +import com.google.cloud.StringEnumValue; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.reflect.Type; +import java.nio.charset.Charset; +import java.util.function.Function; + +final class XmlObjectParser implements ObjectParser { + private final XmlMapper xmlMapper; + + @VisibleForTesting + public XmlObjectParser(XmlMapper xmlMapper) { + this.xmlMapper = xmlMapper; + this.xmlMapper.registerModule(new JavaTimeModule()); + // ensure parsing does not fail if any new field is ever added in the future + this.xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.xmlMapper.registerModule( + new Module() { + @Override + public String getModuleName() { + return this.getClass().getPackage().getName(); + } + + @Override + public Version version() { + return Version.unknownVersion(); + } + + @Override + public void setupModule(SetupContext context) { + context.addSerializers( + new SimpleSerializers( + ImmutableList.of(new StringEnumValueSerializer<>(StorageClass.class)))); + context.addDeserializers( + new SimpleDeserializers( + ImmutableMap.of( + StorageClass.class, + new StringEnumValueDeserializer<>( + StorageClass.class, StorageClass::valueOf)))); + } + }); + } + + @Override + public T parseAndClose(InputStream in, Charset charset, Class dataClass) + throws IOException { + return parseAndClose(new InputStreamReader(in, charset), dataClass); + } + + @Override + public Object parseAndClose(InputStream in, Charset charset, Type dataType) throws IOException { + throw new UnsupportedOperationException( + "XmlObjectParse#" + + CrossTransportUtils.fmtMethodName( + "parseAndClose", InputStream.class, Charset.class, Type.class)); + } + + @Override + public T parseAndClose(Reader reader, Class dataClass) throws IOException { + try (Reader r = reader) { + return xmlMapper.readValue(r, dataClass); + } + } + + @Override + public Object parseAndClose(Reader reader, Type dataType) throws IOException { + throw new UnsupportedOperationException( + "XmlObjectParse#" + + CrossTransportUtils.fmtMethodName("parseAndClose", Reader.class, Type.class)); + } + + private static final class StringEnumValueDeserializer + extends StdDeserializer { + + private final Function constructor; + + private StringEnumValueDeserializer(Class cl, Function constructor) { + super(cl); + this.constructor = constructor; + } + + @Override + public E deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JacksonException { + String s = p.readValueAs(String.class); + if (s == null || s.trim().isEmpty()) { + return null; + } + return constructor.apply(s); + } + } + + private static final class StringEnumValueSerializer + extends StdSerializer { + + private StringEnumValueSerializer(Class cl) { + super(cl); + } + + @Override + public void serialize(E value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeString(value.name()); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/AbortMultipartUploadRequest.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/AbortMultipartUploadRequest.java new file mode 100644 index 0000000000..2da671ff7d --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/AbortMultipartUploadRequest.java @@ -0,0 +1,145 @@ +/* + * 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.multipartupload.model; + +import com.google.api.core.BetaApi; + +/** + * Represents a request to abort a multipart upload. This request is used to stop an in-progress + * multipart upload, deleting any previously uploaded parts. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class AbortMultipartUploadRequest { + private final String bucket; + private final String key; + private final String uploadId; + + private AbortMultipartUploadRequest(Builder builder) { + this.bucket = builder.bucket; + this.key = builder.key; + this.uploadId = builder.uploadId; + } + + /** + * Returns the name of the bucket in which the multipart upload is stored. + * + * @return The bucket name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String bucket() { + return bucket; + } + + /** + * Returns the name of the object that is being uploaded. + * + * @return The object name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String key() { + return key; + } + + /** + * Returns the upload ID of the multipart upload to abort. + * + * @return The upload ID. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String uploadId() { + return uploadId; + } + + /** + * Returns a new builder for creating {@link AbortMultipartUploadRequest} instances. + * + * @return A new {@link Builder}. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating {@link AbortMultipartUploadRequest} instances. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static class Builder { + private String bucket; + private String key; + private String uploadId; + + private Builder() {} + + /** + * Sets the name of the bucket in which the multipart upload is stored. + * + * @param bucket The bucket name. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * Sets the name of the object that is being uploaded. + * + * @param key The object name. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder key(String key) { + this.key = key; + return this; + } + + /** + * Sets the upload ID of the multipart upload to abort. + * + * @param uploadId The upload ID. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder uploadId(String uploadId) { + this.uploadId = uploadId; + return this; + } + + /** + * Builds a new {@link AbortMultipartUploadRequest} instance. + * + * @return A new {@link AbortMultipartUploadRequest}. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public AbortMultipartUploadRequest build() { + return new AbortMultipartUploadRequest(this); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/AbortMultipartUploadResponse.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/AbortMultipartUploadResponse.java new file mode 100644 index 0000000000..050bb68f73 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/AbortMultipartUploadResponse.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.multipartupload.model; + +import com.google.api.core.BetaApi; + +/** + * Represents a response to an abort multipart upload request. This class is currently empty as the + * abort operation does not return any specific data in its response body. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class AbortMultipartUploadResponse {} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompleteMultipartUploadRequest.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompleteMultipartUploadRequest.java new file mode 100644 index 0000000000..291a10d3af --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompleteMultipartUploadRequest.java @@ -0,0 +1,205 @@ +/* + * 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.multipartupload.model; + +import com.google.api.core.BetaApi; +import com.google.common.base.MoreObjects; +import java.util.Objects; + +/** + * Represents a request to complete a multipart upload. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class CompleteMultipartUploadRequest { + + private final String bucket; + private final String key; + private final String uploadId; + private final CompletedMultipartUpload multipartUpload; + + private CompleteMultipartUploadRequest(Builder builder) { + this.bucket = builder.bucket; + this.key = builder.key; + this.uploadId = builder.uploadId; + this.multipartUpload = builder.multipartUpload; + } + + /** + * Returns the bucket name. + * + * @return The bucket name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String bucket() { + return bucket; + } + + /** + * Returns the object name. + * + * @return The object name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String key() { + return key; + } + + /** + * Returns the upload ID of the multipart upload. + * + * @return The upload ID. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String uploadId() { + return uploadId; + } + + /** + * Returns the {@link CompletedMultipartUpload} payload for this request. + * + * @return The {@link CompletedMultipartUpload} payload. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public CompletedMultipartUpload multipartUpload() { + return multipartUpload; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CompleteMultipartUploadRequest)) { + return false; + } + CompleteMultipartUploadRequest that = (CompleteMultipartUploadRequest) o; + return Objects.equals(bucket, that.bucket) + && Objects.equals(key, that.key) + && Objects.equals(uploadId, that.uploadId) + && Objects.equals(multipartUpload, that.multipartUpload); + } + + @Override + public int hashCode() { + return Objects.hash(bucket, key, uploadId, multipartUpload); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("bucket", bucket) + .add("key", key) + .add("uploadId", uploadId) + .add("completedMultipartUpload", multipartUpload) + .toString(); + } + + /** + * Creates a new builder for {@link CompleteMultipartUploadRequest}. + * + * @return A new builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link CompleteMultipartUploadRequest}. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static class Builder { + private String bucket; + private String key; + private String uploadId; + private CompletedMultipartUpload multipartUpload; + + private Builder() {} + + /** + * Sets the bucket name. + * + * @param bucket The bucket name. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * Sets the object name. + * + * @param key The object name. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder key(String key) { + this.key = key; + return this; + } + + /** + * Sets the upload ID of the multipart upload. + * + * @param uploadId The upload ID. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder uploadId(String uploadId) { + this.uploadId = uploadId; + return this; + } + + /** + * Sets the {@link CompletedMultipartUpload} payload for this request. + * + * @param completedMultipartUpload The {@link CompletedMultipartUpload} payload. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder multipartUpload(CompletedMultipartUpload completedMultipartUpload) { + this.multipartUpload = completedMultipartUpload; + return this; + } + + /** + * Builds the {@link CompleteMultipartUploadRequest} object. + * + * @return The new {@link CompleteMultipartUploadRequest} object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public CompleteMultipartUploadRequest build() { + return new CompleteMultipartUploadRequest(this); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompleteMultipartUploadResponse.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompleteMultipartUploadResponse.java new file mode 100644 index 0000000000..52d16dad6d --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompleteMultipartUploadResponse.java @@ -0,0 +1,247 @@ +/* + * 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.multipartupload.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.api.core.BetaApi; +import com.google.common.base.MoreObjects; +import java.util.Objects; + +/** + * Represents the response from a completed multipart upload. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +@JsonDeserialize(builder = CompleteMultipartUploadResponse.Builder.class) +public final class CompleteMultipartUploadResponse { + + private final String location; + private final String bucket; + private final String key; + private final String etag; + private final String crc32c; + + private CompleteMultipartUploadResponse(Builder builder) { + this.location = builder.location; + this.bucket = builder.bucket; + this.key = builder.key; + this.etag = builder.etag; + this.crc32c = builder.crc32c; + } + + /** + * Returns the URL of the completed object. + * + * @return The URL of the completed object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + @JsonProperty("Location") + public String location() { + return location; + } + + /** + * Returns the bucket name. + * + * @return The bucket name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + @JsonProperty("Bucket") + public String bucket() { + return bucket; + } + + /** + * Returns the object name. + * + * @return The object name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + @JsonProperty("Key") + public String key() { + return key; + } + + /** + * Returns the ETag of the completed object. + * + * @return The ETag of the completed object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + @JsonProperty("ETag") + public String etag() { + return etag; + } + + /** + * Returns the CRC32C checksum of the completed object. + * + * @return The CRC32C checksum of the completed object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String crc32c() { + return crc32c; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CompleteMultipartUploadResponse)) { + return false; + } + CompleteMultipartUploadResponse that = (CompleteMultipartUploadResponse) o; + return Objects.equals(location, that.location) + && Objects.equals(bucket, that.bucket) + && Objects.equals(key, that.key) + && Objects.equals(etag, that.etag) + && Objects.equals(crc32c, that.crc32c); + } + + @Override + public int hashCode() { + return Objects.hash(location, bucket, key, etag, crc32c); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("location", location) + .add("bucket", bucket) + .add("key", key) + .add("etag", etag) + .add("crc32c", crc32c) + .toString(); + } + + /** + * Creates a new builder for {@link CompleteMultipartUploadResponse}. + * + * @return A new builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link CompleteMultipartUploadResponse}. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + @JsonPOJOBuilder(buildMethodName = "build") + public static class Builder { + private String location; + private String bucket; + private String key; + private String etag; + private String crc32c; + + private Builder() {} + + /** + * Sets the URL of the completed object. + * + * @param location The URL of the completed object. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + @JsonProperty("Location") + public Builder location(String location) { + this.location = location; + return this; + } + + /** + * Sets the bucket name. + * + * @param bucket The bucket name. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + @JsonProperty("Bucket") + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * Sets the object name. + * + * @param key The object name. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + @JsonProperty("Key") + public Builder key(String key) { + this.key = key; + return this; + } + + /** + * Sets the ETag of the completed object. + * + * @param etag The ETag of the completed object. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + @JsonProperty("ETag") + public Builder etag(String etag) { + this.etag = etag; + return this; + } + + /** + * Sets the CRC32C checksum of the completed object. + * + * @param crc32c The CRC32C checksum of the completed object. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder crc32c(String crc32c) { + this.crc32c = crc32c; + return this; + } + + /** + * Builds the {@link CompleteMultipartUploadResponse} object. + * + * @return The new {@link CompleteMultipartUploadResponse} object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public CompleteMultipartUploadResponse build() { + return new CompleteMultipartUploadResponse(this); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompletedMultipartUpload.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompletedMultipartUpload.java new file mode 100644 index 0000000000..0d008bf5fc --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompletedMultipartUpload.java @@ -0,0 +1,123 @@ +/* + * 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.multipartupload.model; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import com.google.api.core.BetaApi; +import com.google.common.base.MoreObjects; +import java.util.List; +import java.util.Objects; + +/** + * Represents the XML payload for a completed multipart upload. This is used in the request body + * when completing a multipart upload. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +@JacksonXmlRootElement(localName = "CompleteMultipartUpload") +public class CompletedMultipartUpload { + + @JacksonXmlElementWrapper(useWrapping = false) + @JacksonXmlProperty(localName = "Part") + private final List completedPartList; + + private CompletedMultipartUpload(Builder builder) { + this.completedPartList = builder.parts; + } + + /** + * Returns the list of completed parts for this multipart upload. + * + * @return The list of completed parts. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public List parts() { + return completedPartList; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CompletedMultipartUpload)) { + return false; + } + CompletedMultipartUpload that = (CompletedMultipartUpload) o; + return Objects.equals(completedPartList, that.completedPartList); + } + + @Override + public int hashCode() { + return Objects.hash(completedPartList); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("completedPartList", completedPartList).toString(); + } + + /** + * Creates a new builder for {@link CompletedMultipartUpload}. + * + * @return A new builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link CompletedMultipartUpload}. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static class Builder { + private List parts; + + private Builder() {} + + /** + * Sets the list of completed parts for the multipart upload. + * + * @param completedPartList The list of completed parts. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder parts(List completedPartList) { + this.parts = completedPartList; + return this; + } + + /** + * Builds the {@link CompletedMultipartUpload} object. + * + * @return The new {@link CompletedMultipartUpload} object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public CompletedMultipartUpload build() { + return new CompletedMultipartUpload(this); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompletedPart.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompletedPart.java new file mode 100644 index 0000000000..8be7ded883 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CompletedPart.java @@ -0,0 +1,120 @@ +/* + * 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.multipartupload.model; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.google.api.core.BetaApi; + +/** + * Represents a completed part of a multipart upload. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class CompletedPart { + + @JacksonXmlProperty(localName = "PartNumber") + private final int partNumber; + + @JacksonXmlProperty(localName = "ETag") + private final String eTag; + + private CompletedPart(int partNumber, String eTag) { + this.partNumber = partNumber; + this.eTag = eTag; + } + + /** + * Creates a new builder for {@link CompletedPart}. + * + * @return A new builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * Returns the part number of this completed part. + * + * @return The part number. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public int partNumber() { + return partNumber; + } + + /** + * Returns the ETag of this completed part. + * + * @return The ETag. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String eTag() { + return eTag; + } + + /** + * Builder for {@link CompletedPart}. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static class Builder { + private int partNumber; + private String etag; + + /** + * Sets the part number of the completed part. + * + * @param partNumber The part number. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder partNumber(int partNumber) { + this.partNumber = partNumber; + return this; + } + + /** + * Sets the ETag of the completed part. + * + * @param etag The ETag. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder eTag(String etag) { + this.etag = etag; + return this; + } + + /** + * Builds the {@link CompletedPart} object. + * + * @return The new {@link CompletedPart} object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public CompletedPart build() { + return new CompletedPart(partNumber, etag); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CreateMultipartUploadRequest.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CreateMultipartUploadRequest.java new file mode 100644 index 0000000000..ae1fa1186c --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CreateMultipartUploadRequest.java @@ -0,0 +1,399 @@ +/* + * 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.multipartupload.model; + +import com.google.api.core.BetaApi; +import com.google.cloud.storage.Storage.PredefinedAcl; +import com.google.cloud.storage.StorageClass; +import com.google.common.base.MoreObjects; +import java.time.OffsetDateTime; +import java.util.Map; +import java.util.Objects; + +/** + * Represents a request to initiate a multipart upload. This class holds all the necessary + * information to create a new multipart upload session. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class CreateMultipartUploadRequest { + private final String bucket; + private final String key; + private final PredefinedAcl cannedAcl; + private final String contentType; + private final Map metadata; + private final StorageClass storageClass; + private final OffsetDateTime customTime; + private final String kmsKeyName; + private final ObjectLockMode objectLockMode; + private final OffsetDateTime objectLockRetainUntilDate; + + private CreateMultipartUploadRequest(Builder builder) { + this.bucket = builder.bucket; + this.key = builder.key; + this.cannedAcl = builder.cannedAcl; + this.contentType = builder.contentType; + this.metadata = builder.metadata; + this.storageClass = builder.storageClass; + this.customTime = builder.customTime; + this.kmsKeyName = builder.kmsKeyName; + this.objectLockMode = builder.objectLockMode; + this.objectLockRetainUntilDate = builder.objectLockRetainUntilDate; + } + + /** + * Returns the name of the bucket to which the object is being uploaded. + * + * @return The bucket name + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String bucket() { + return bucket; + } + + /** + * Returns the name of the object. + * + * @see Object Naming + * @return The object name + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String key() { + return key; + } + + /** + * Returns a canned ACL to apply to the object. + * + * @return The canned ACL + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public PredefinedAcl getCannedAcl() { + return cannedAcl; + } + + /** + * Returns the MIME type of the data you are uploading. + * + * @return The Content-Type + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getContentType() { + return contentType; + } + + /** + * Returns the custom metadata of the object. + * + * @return The custom metadata + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Map getMetadata() { + return metadata; + } + + /** + * Returns the storage class for the object. + * + * @return The Storage-Class + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public StorageClass getStorageClass() { + return storageClass; + } + + /** + * Returns a user-specified date and time. + * + * @return The custom time + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public OffsetDateTime getCustomTime() { + return customTime; + } + + /** + * Returns the customer-managed encryption key to use to encrypt the object. + * + * @return The Cloud KMS key + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getKmsKeyName() { + return kmsKeyName; + } + + /** + * Returns the mode of the object's retention configuration. + * + * @return The object lock mode + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public ObjectLockMode getObjectLockMode() { + return objectLockMode; + } + + /** + * Returns the date that determines the time until which the object is retained as immutable. + * + * @return The object lock retention until date + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public OffsetDateTime getObjectLockRetainUntilDate() { + return objectLockRetainUntilDate; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CreateMultipartUploadRequest)) { + return false; + } + CreateMultipartUploadRequest that = (CreateMultipartUploadRequest) o; + return Objects.equals(bucket, that.bucket) + && Objects.equals(key, that.key) + && cannedAcl == that.cannedAcl + && Objects.equals(contentType, that.contentType) + && Objects.equals(metadata, that.metadata) + && Objects.equals(storageClass, that.storageClass) + && Objects.equals(customTime, that.customTime) + && Objects.equals(kmsKeyName, that.kmsKeyName) + && objectLockMode == that.objectLockMode + && Objects.equals(objectLockRetainUntilDate, that.objectLockRetainUntilDate); + } + + @Override + public int hashCode() { + return Objects.hash( + bucket, + key, + cannedAcl, + contentType, + metadata, + storageClass, + customTime, + kmsKeyName, + objectLockMode, + objectLockRetainUntilDate); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("bucket", bucket) + .add("key", key) + .add("cannedAcl", cannedAcl) + .add("contentType", contentType) + .add("metadata", metadata) + .add("storageClass", storageClass) + .add("customTime", customTime) + .add("kmsKeyName", kmsKeyName) + .add("objectLockMode", objectLockMode) + .add("objectLockRetainUntilDate", objectLockRetainUntilDate) + .toString(); + } + + /** + * Returns a new {@link Builder} for creating a {@link CreateMultipartUploadRequest}. + * + * @return a new builder + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for {@link CreateMultipartUploadRequest}. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static final class Builder { + private String bucket; + private String key; + private PredefinedAcl cannedAcl; + private String contentType; + private Map metadata; + private StorageClass storageClass; + private OffsetDateTime customTime; + private String kmsKeyName; + private ObjectLockMode objectLockMode; + private OffsetDateTime objectLockRetainUntilDate; + + private Builder() {} + + /** + * The bucket to which the object is being uploaded. + * + * @param bucket The bucket name + * @return this builder + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * The name of the object. + * + * @see Object Naming + * @param key The object name + * @return this builder + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder key(String key) { + this.key = key; + return this; + } + + /** + * A canned ACL to apply to the object. + * + * @param cannedAcl The canned ACL + * @return this builder + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder cannedAcl(PredefinedAcl cannedAcl) { + this.cannedAcl = cannedAcl; + return this; + } + + /** + * The MIME type of the data you are uploading. + * + * @param contentType The Content-Type + * @return this builder + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder contentType(String contentType) { + this.contentType = contentType; + return this; + } + + /** + * The custom metadata of the object. + * + * @param metadata The custom metadata + * @return this builder + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder metadata(Map metadata) { + this.metadata = metadata; + return this; + } + + /** + * Gives each part of the upload and the resulting object a storage class besides the default + * storage class of the associated bucket. + * + * @param storageClass The Storage-Class + * @return this builder + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder storageClass(StorageClass storageClass) { + this.storageClass = storageClass; + return this; + } + + /** + * A user-specified date and time. + * + * @param customTime The custom time + * @return this builder + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder customTime(OffsetDateTime customTime) { + this.customTime = customTime; + return this; + } + + /** + * The customer-managed encryption key to use to encrypt the object. Refer: Customer + * Managed Keys + * + * @param kmsKeyName The Cloud KMS key + * @return this builder + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder kmsKeyName(String kmsKeyName) { + this.kmsKeyName = kmsKeyName; + return this; + } + + /** + * Mode of the object's retention configuration. GOVERNANCE corresponds to unlocked mode, and + * COMPLIANCE corresponds to locked mode. + * + * @param objectLockMode The object lock mode + * @return this builder + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder objectLockMode(ObjectLockMode objectLockMode) { + this.objectLockMode = objectLockMode; + return this; + } + + /** + * Date that determines the time until which the object is retained as immutable. + * + * @param objectLockRetainUntilDate The object lock retention until date + * @return this builder + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder objectLockRetainUntilDate(OffsetDateTime objectLockRetainUntilDate) { + this.objectLockRetainUntilDate = objectLockRetainUntilDate; + return this; + } + + /** + * Creates a new {@link CreateMultipartUploadRequest} object. + * + * @return a new {@link CreateMultipartUploadRequest} object + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public CreateMultipartUploadRequest build() { + return new CreateMultipartUploadRequest(this); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CreateMultipartUploadResponse.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CreateMultipartUploadResponse.java new file mode 100644 index 0000000000..87afb7fd62 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/CreateMultipartUploadResponse.java @@ -0,0 +1,188 @@ +/* + * 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.multipartupload.model; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import com.google.api.core.BetaApi; +import com.google.common.base.MoreObjects; +import java.util.Objects; + +/** + * Represents the response from a CreateMultipartUpload request. This class encapsulates the details + * of the initiated multipart upload, including the bucket, key, and the unique upload ID. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@JacksonXmlRootElement(localName = "InitiateMultipartUploadResult") +@BetaApi +public final class CreateMultipartUploadResponse { + + @JacksonXmlProperty(localName = "Bucket") + private String bucket; + + @JacksonXmlProperty(localName = "Key") + private String key; + + @JacksonXmlProperty(localName = "UploadId") + private String uploadId; + + private CreateMultipartUploadResponse() {} + + private CreateMultipartUploadResponse(Builder builder) { + this.bucket = builder.bucket; + this.key = builder.key; + this.uploadId = builder.uploadId; + } + + /** + * Returns the name of the bucket where the multipart upload was initiated. + * + * @return The bucket name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String bucket() { + return bucket; + } + + /** + * Returns the key (object name) for which the multipart upload was initiated. + * + * @return The object key. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String key() { + return key; + } + + /** + * Returns the unique identifier for this multipart upload. This ID must be included in all + * subsequent requests related to this upload (e.g., uploading parts, completing the upload). + * + * @return The upload ID. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String uploadId() { + return uploadId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CreateMultipartUploadResponse)) { + return false; + } + CreateMultipartUploadResponse that = (CreateMultipartUploadResponse) o; + return Objects.equals(bucket, that.bucket) + && Objects.equals(key, that.key) + && Objects.equals(uploadId, that.uploadId); + } + + @Override + public int hashCode() { + return Objects.hash(bucket, key, uploadId); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("bucket", bucket) + .add("key", key) + .add("uploadId", uploadId) + .toString(); + } + + /** + * Creates a new builder for {@link CreateMultipartUploadResponse}. + * + * @return A new builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for {@link CreateMultipartUploadResponse} objects. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static final class Builder { + private String bucket; + private String key; + private String uploadId; + + private Builder() {} + + /** + * Sets the bucket name for the multipart upload. + * + * @param bucket The bucket name. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * Sets the key (object name) for the multipart upload. + * + * @param key The object key. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder key(String key) { + this.key = key; + return this; + } + + /** + * Sets the upload ID for the multipart upload. + * + * @param uploadId The upload ID. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder uploadId(String uploadId) { + this.uploadId = uploadId; + return this; + } + + /** + * Builds a new {@link CreateMultipartUploadResponse} object. + * + * @return A new {@link CreateMultipartUploadResponse} object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public CreateMultipartUploadResponse build() { + return new CreateMultipartUploadResponse(this); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListPartsRequest.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListPartsRequest.java new file mode 100644 index 0000000000..2062bdfeb2 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListPartsRequest.java @@ -0,0 +1,237 @@ +/* + * 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.multipartupload.model; + +import com.google.api.core.BetaApi; +import com.google.common.base.MoreObjects; +import java.util.Objects; + +/** + * Represents a request to list the parts of a multipart upload. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class ListPartsRequest { + private final String bucket; + + private final String key; + + private final String uploadId; + + private final Integer maxParts; + + private final Integer partNumberMarker; + + private ListPartsRequest(Builder builder) { + this.bucket = builder.bucket; + this.key = builder.key; + this.uploadId = builder.uploadId; + this.maxParts = builder.maxParts; + this.partNumberMarker = builder.partNumberMarker; + } + + /** + * Returns the bucket name. + * + * @return the bucket name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String bucket() { + return bucket; + } + + /** + * Returns the object name. + * + * @return the object name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String key() { + return key; + } + + /** + * Returns the upload ID. + * + * @return the upload ID. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String uploadId() { + return uploadId; + } + + /** + * Returns the maximum number of parts to return. + * + * @return the maximum number of parts to return. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Integer getMaxParts() { + return maxParts; + } + + /** + * Returns the part number marker. + * + * @return the part number marker. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Integer getPartNumberMarker() { + return partNumberMarker; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ListPartsRequest)) { + return false; + } + ListPartsRequest that = (ListPartsRequest) o; + return Objects.equals(bucket, that.bucket) + && Objects.equals(key, that.key) + && Objects.equals(uploadId, that.uploadId) + && Objects.equals(maxParts, that.maxParts) + && Objects.equals(partNumberMarker, that.partNumberMarker); + } + + @Override + public int hashCode() { + return Objects.hash(bucket, key, uploadId, maxParts, partNumberMarker); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("bucket", bucket) + .add("key", key) + .add("uploadId", uploadId) + .add("maxParts", maxParts) + .add("partNumberMarker", partNumberMarker) + .toString(); + } + + /** + * Returns a new builder for this class. + * + * @return a new builder for this class. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for {@link ListPartsRequest}. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static class Builder { + private String bucket; + private String key; + private String uploadId; + private Integer maxParts; + private Integer partNumberMarker; + + private Builder() {} + + /** + * Sets the bucket name. + * + * @param bucket the bucket name. + * @return this builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * Sets the object name. + * + * @param key the object name. + * @return this builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder key(String key) { + this.key = key; + return this; + } + + /** + * Sets the upload ID. + * + * @param uploadId the upload ID. + * @return this builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder uploadId(String uploadId) { + this.uploadId = uploadId; + return this; + } + + /** + * Sets the maximum number of parts to return. + * + * @param maxParts the maximum number of parts to return. + * @return this builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder maxParts(Integer maxParts) { + this.maxParts = maxParts; + return this; + } + + /** + * Sets the part number marker. + * + * @param partNumberMarker the part number marker. + * @return this builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder partNumberMarker(Integer partNumberMarker) { + this.partNumberMarker = partNumberMarker; + return this; + } + + /** + * Builds a new {@link ListPartsRequest} object. + * + * @return a new {@link ListPartsRequest} object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public ListPartsRequest build() { + return new ListPartsRequest(this); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListPartsResponse.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListPartsResponse.java new file mode 100644 index 0000000000..0b8f82c5a0 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ListPartsResponse.java @@ -0,0 +1,385 @@ +/* + * 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.multipartupload.model; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.google.api.core.BetaApi; +import com.google.cloud.storage.StorageClass; +import com.google.common.base.MoreObjects; +import java.util.List; +import java.util.Objects; + +/** + * Represents a response to a list parts request. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class ListPartsResponse { + + @JacksonXmlProperty(localName = "Bucket") + private String bucket; + + @JacksonXmlProperty(localName = "Key") + private String key; + + @JacksonXmlProperty(localName = "UploadId") + private String uploadId; + + @JacksonXmlProperty(localName = "PartNumberMarker") + private int partNumberMarker; + + @JacksonXmlProperty(localName = "NextPartNumberMarker") + private int nextPartNumberMarker; + + @JacksonXmlProperty(localName = "MaxParts") + private int maxParts; + + @JsonAlias("truncated") // S3 returns "truncated", GCS returns "IsTruncated" + @JacksonXmlProperty(localName = "IsTruncated") + private boolean isTruncated; + + @JacksonXmlProperty(localName = "StorageClass") + private StorageClass storageClass; + + @JacksonXmlElementWrapper(useWrapping = false) + @JacksonXmlProperty(localName = "Part") + private List parts; + + private ListPartsResponse() {} + + private ListPartsResponse(Builder builder) { + this.bucket = builder.bucket; + this.key = builder.key; + this.uploadId = builder.uploadId; + this.partNumberMarker = builder.partNumberMarker; + this.nextPartNumberMarker = builder.nextPartNumberMarker; + this.maxParts = builder.maxParts; + this.isTruncated = builder.isTruncated; + this.storageClass = builder.storageClass; + this.parts = builder.parts; + } + + /** + * Creates a new {@code Builder} for {@code ListPartsResponse} objects. + * + * @return A new {@code Builder} instance. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * Returns the bucket name. + * + * @return the bucket name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getBucket() { + return bucket; + } + + /** + * Returns the object name. + * + * @return the object name. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getKey() { + return key; + } + + /** + * Returns the upload ID. + * + * @return the upload ID. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String getUploadId() { + return uploadId; + } + + /** + * Returns the part number marker. + * + * @return the part number marker. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public int getPartNumberMarker() { + return partNumberMarker; + } + + /** + * Returns the next part number marker. + * + * @return the next part number marker. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public int getNextPartNumberMarker() { + return nextPartNumberMarker; + } + + /** + * Returns the maximum number of parts to return. + * + * @return the maximum number of parts to return. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public int getMaxParts() { + return maxParts; + } + + /** + * Returns true if the response is truncated. + * + * @return true if the response is truncated. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public boolean isTruncated() { + return isTruncated; + } + + /** + * Returns the storage class of the object. + * + * @return the storage class of the object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public StorageClass getStorageClass() { + return storageClass; + } + + /** + * Returns the list of parts. + * + * @return the list of parts. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public List getParts() { + return parts; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ListPartsResponse)) { + return false; + } + ListPartsResponse that = (ListPartsResponse) o; + return Objects.equals(bucket, that.bucket) + && Objects.equals(key, that.key) + && Objects.equals(uploadId, that.uploadId) + && Objects.equals(partNumberMarker, that.partNumberMarker) + && Objects.equals(nextPartNumberMarker, that.nextPartNumberMarker) + && Objects.equals(maxParts, that.maxParts) + && Objects.equals(isTruncated, that.isTruncated) + && Objects.equals(storageClass, that.storageClass) + && Objects.equals(parts, that.parts); + } + + @Override + public int hashCode() { + return Objects.hash( + bucket, + key, + uploadId, + partNumberMarker, + nextPartNumberMarker, + maxParts, + isTruncated, + storageClass, + parts); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("bucket", bucket) + .add("key", key) + .add("uploadId", uploadId) + .add("partNumberMarker", partNumberMarker) + .add("nextPartNumberMarker", nextPartNumberMarker) + .add("maxParts", maxParts) + .add("isTruncated", isTruncated) + .add("storageClass", storageClass) + .add("parts", parts) + .toString(); + } + + /** + * Builder for {@code ListPartsResponse}. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static final class Builder { + private String bucket; + private String key; + private String uploadId; + private int partNumberMarker; + private int nextPartNumberMarker; + private int maxParts; + private boolean isTruncated; + private StorageClass storageClass; + private List parts; + + private Builder() {} + + /** + * Sets the bucket name. + * + * @param bucket The bucket name. + * @return The builder instance. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setBucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * Sets the object name. + * + * @param key The object name. + * @return The builder instance. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setKey(String key) { + this.key = key; + return this; + } + + /** + * Sets the upload ID. + * + * @param uploadId The upload ID. + * @return The builder instance. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setUploadId(String uploadId) { + this.uploadId = uploadId; + return this; + } + + /** + * Sets the part number marker. + * + * @param partNumberMarker The part number marker. + * @return The builder instance. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setPartNumberMarker(int partNumberMarker) { + this.partNumberMarker = partNumberMarker; + return this; + } + + /** + * Sets the next part number marker. + * + * @param nextPartNumberMarker The next part number marker. + * @return The builder instance. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setNextPartNumberMarker(int nextPartNumberMarker) { + this.nextPartNumberMarker = nextPartNumberMarker; + return this; + } + + /** + * Sets the maximum number of parts to return. + * + * @param maxParts The maximum number of parts to return. + * @return The builder instance. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setMaxParts(int maxParts) { + this.maxParts = maxParts; + return this; + } + + /** + * Sets whether the response is truncated. + * + * @param isTruncated True if the response is truncated, false otherwise. + * @return The builder instance. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setIsTruncated(boolean isTruncated) { + this.isTruncated = isTruncated; + return this; + } + + /** + * Sets the storage class of the object. + * + * @param storageClass The storage class of the object. + * @return The builder instance. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setStorageClass(StorageClass storageClass) { + this.storageClass = storageClass; + return this; + } + + /** + * Sets the list of parts. + * + * @param parts The list of parts. + * @return The builder instance. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder setParts(List parts) { + this.parts = parts; + return this; + } + + /** + * Builds a {@code ListPartsResponse} object. + * + * @return A new {@code ListPartsResponse} instance. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public ListPartsResponse build() { + return new ListPartsResponse(this); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ObjectLockMode.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ObjectLockMode.java new file mode 100644 index 0000000000..da70a9130b --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/ObjectLockMode.java @@ -0,0 +1,91 @@ +/* + * 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.multipartupload.model; + +import com.google.api.core.ApiFunction; +import com.google.api.core.BetaApi; +import com.google.cloud.StringEnumType; +import com.google.cloud.StringEnumValue; + +/** + * Represents the object lock mode. See https://cloud.google.com/storage/docs/object-lock + * for details. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class ObjectLockMode extends StringEnumValue { + private static final long serialVersionUID = -1882734434792102329L; + + private ObjectLockMode(String constant) { + super(constant); + } + + private static final ApiFunction CONSTRUCTOR = ObjectLockMode::new; + + private static final StringEnumType type = + new StringEnumType<>(ObjectLockMode.class, CONSTRUCTOR); + + /** + * Governance mode. See https://cloud.google.com/storage/docs/object-lock + * for details. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi public static final ObjectLockMode GOVERNANCE = type.createAndRegister("GOVERNANCE"); + + /** + * Compliance mode. See https://cloud.google.com/storage/docs/object-lock + * for details. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi public static final ObjectLockMode COMPLIANCE = type.createAndRegister("COMPLIANCE"); + + /** + * Get the ObjectLockMode for the given String constant, and throw an exception if the constant is + * not recognized. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static ObjectLockMode valueOfStrict(String constant) { + return type.valueOfStrict(constant); + } + + /** + * Get the ObjectLockMode for the given String constant, and allow unrecognized values. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static ObjectLockMode valueOf(String constant) { + return type.valueOf(constant); + } + + /** + * Return the known values for ObjectLockMode. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static ObjectLockMode[] values() { + return type.values(); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/Part.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/Part.java new file mode 100644 index 0000000000..6309316c7d --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/Part.java @@ -0,0 +1,217 @@ +/* + * 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.multipartupload.model; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.google.api.core.BetaApi; +import com.google.common.base.MoreObjects; +import java.time.OffsetDateTime; +import java.util.Objects; + +/** + * Represents a part of a multipart upload. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class Part { + + @JacksonXmlProperty(localName = "PartNumber") + private int partNumber; + + @JacksonXmlProperty(localName = "ETag") + private String eTag; + + @JacksonXmlProperty(localName = "Size") + private long size; + + @JacksonXmlProperty(localName = "LastModified") + private OffsetDateTime lastModified; + + // for jackson + private Part() {} + + private Part(Builder builder) { + this.partNumber = builder.partNumber; + this.eTag = builder.eTag; + this.size = builder.size; + this.lastModified = builder.lastModified; + } + + /** + * Returns the part number. + * + * @return the part number. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public int partNumber() { + return partNumber; + } + + /** + * Returns the ETag of the part. + * + * @return the ETag of the part. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String eTag() { + return eTag; + } + + /** + * Returns the size of the part. + * + * @return the size of the part. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public long size() { + return size; + } + + /** + * Returns the last modified time of the part. + * + * @return the last modified time of the part. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public OffsetDateTime lastModified() { + return lastModified; + } + + /** + * Returns a new builder for this class. + * + * @return a new builder for this class. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Part)) { + return false; + } + Part that = (Part) o; + return Objects.equals(partNumber, that.partNumber) + && Objects.equals(eTag, that.eTag) + && Objects.equals(size, that.size) + && Objects.equals(lastModified, that.lastModified); + } + + @Override + public int hashCode() { + return Objects.hash(partNumber, eTag, size, lastModified); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("partNumber", partNumber) + .add("eTag", eTag) + .add("size", size) + .add("lastModified", lastModified) + .toString(); + } + + /** + * A builder for {@link Part}. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static final class Builder { + private int partNumber; + private String eTag; + private long size; + private OffsetDateTime lastModified; + + private Builder() {} + + /** + * Sets the part number. + * + * @param partNumber the part number. + * @return this builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder partNumber(int partNumber) { + this.partNumber = partNumber; + return this; + } + + /** + * Sets the ETag of the part. + * + * @param eTag the ETag of the part. + * @return this builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder eTag(String eTag) { + this.eTag = eTag; + return this; + } + + /** + * Sets the size of the part. + * + * @param size the size of the part. + * @return this builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder size(long size) { + this.size = size; + return this; + } + + /** + * Sets the last modified time of the part. + * + * @param lastModified the last modified time of the part. + * @return this builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder lastModified(OffsetDateTime lastModified) { + this.lastModified = lastModified; + return this; + } + + /** + * Builds a new {@link Part} object. + * + * @return a new {@link Part} object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Part build() { + return new Part(this); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/UploadPartRequest.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/UploadPartRequest.java new file mode 100644 index 0000000000..9d07cf7fb0 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/UploadPartRequest.java @@ -0,0 +1,206 @@ +/* + * 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.multipartupload.model; + +import com.google.api.core.BetaApi; +import com.google.common.base.MoreObjects; +import java.util.Objects; + +/** + * An object to represent an upload part request. An upload part request is used to upload a single + * part of a multipart upload. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class UploadPartRequest { + + private final String bucket; + private final String key; + private final int partNumber; + private final String uploadId; + + private UploadPartRequest(Builder builder) { + this.bucket = builder.bucket; + this.key = builder.key; + this.partNumber = builder.partNumber; + this.uploadId = builder.uploadId; + } + + /** + * Returns the bucket to upload the part to. + * + * @return The bucket to upload the part to. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String bucket() { + return bucket; + } + + /** + * Returns the key of the object to upload the part to. + * + * @return The key of the object to upload the part to. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String key() { + return key; + } + + /** + * Returns the part number of the part to upload. + * + * @return The part number of the part to upload. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public int partNumber() { + return partNumber; + } + + /** + * Returns the upload ID of the multipart upload. + * + * @return The upload ID of the multipart upload. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String uploadId() { + return uploadId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof UploadPartRequest)) { + return false; + } + UploadPartRequest that = (UploadPartRequest) o; + return partNumber == that.partNumber + && Objects.equals(bucket, that.bucket) + && Objects.equals(key, that.key) + && Objects.equals(uploadId, that.uploadId); + } + + @Override + public int hashCode() { + return Objects.hash(bucket, key, partNumber, uploadId); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("bucket", bucket) + .add("key", key) + .add("partNumber", partNumber) + .add("uploadId", uploadId) + .toString(); + } + + /** + * Returns a new builder for an {@link UploadPartRequest}. + * + * @return A new builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for {@link UploadPartRequest}. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static class Builder { + private String bucket; + private String key; + private int partNumber; + private String uploadId; + + private Builder() {} + + /** + * Sets the bucket to upload the part to. + * + * @param bucket The bucket to upload the part to. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * Sets the key of the object to upload the part to. + * + * @param key The key of the object to upload the part to. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder key(String key) { + this.key = key; + return this; + } + + /** + * Sets the part number of the part to upload. + * + * @param partNumber The part number of the part to upload. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder partNumber(int partNumber) { + this.partNumber = partNumber; + return this; + } + + /** + * Sets the upload ID of the multipart upload. + * + * @param uploadId The upload ID of the multipart upload. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder uploadId(String uploadId) { + this.uploadId = uploadId; + return this; + } + + /** + * Builds the {@link UploadPartRequest}. + * + * @return The built {@link UploadPartRequest}. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public UploadPartRequest build() { + return new UploadPartRequest(this); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/UploadPartResponse.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/UploadPartResponse.java new file mode 100644 index 0000000000..30dc72b0f7 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/multipartupload/model/UploadPartResponse.java @@ -0,0 +1,144 @@ +/* + * 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.multipartupload.model; + +import com.google.api.core.BetaApi; +import com.google.common.base.MoreObjects; +import java.util.Objects; + +/** + * Represents the response from uploading a part in a multipart upload. It contains the ETag and + * checksums of the uploaded part. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ +@BetaApi +public final class UploadPartResponse { + + private final String eTag; + private final String md5; + + private UploadPartResponse(Builder builder) { + this.eTag = builder.etag; + this.md5 = builder.md5; + } + + /** + * Returns the ETag of the uploaded part. + * + * @return The ETag. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String eTag() { + return eTag; + } + + /** + * Returns the MD5 hash of the uploaded part. + * + * @return The MD5 hash. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public String md5() { + return md5; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof UploadPartResponse)) { + return false; + } + UploadPartResponse that = (UploadPartResponse) o; + return Objects.equals(eTag, that.eTag) && Objects.equals(md5, that.md5); + } + + @Override + public int hashCode() { + return Objects.hash(eTag, md5); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("etag", eTag).add("md5", md5).toString(); + } + + /** + * Creates a new builder for creating an {@code UploadPartResponse}. + * + * @return A new builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating {@code UploadPartResponse} instances. + * + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public static class Builder { + private String etag; + private String md5; + + private Builder() {} + + /** + * Sets the ETag for the uploaded part. + * + * @param etag The ETag. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder eTag(String etag) { + this.etag = etag; + return this; + } + + /** + * Sets the MD5 hash for the uploaded part. + * + * @param md5 The MD5 hash. + * @return This builder. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public Builder md5(String md5) { + this.md5 = md5; + return this; + } + + /** + * Builds the {@code UploadPartResponse} object. + * + * @return The built {@code UploadPartResponse} object. + * @since 2.60.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public UploadPartResponse build() { + return new UploadPartResponse(this); + } + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ChecksumResponseParserTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ChecksumResponseParserTest.java new file mode 100644 index 0000000000..4dacacff90 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ChecksumResponseParserTest.java @@ -0,0 +1,102 @@ +/* + * 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; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.testing.http.MockHttpTransport; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; +import com.google.cloud.storage.multipartupload.model.UploadPartResponse; +import java.io.IOException; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class ChecksumResponseParserTest { + + @Test + public void testParse() throws IOException { + HttpResponse response = + createHttpResponse("\"test-etag\"", "crc32c=AAAAAA==,md5=rL0Y20zC+Fzt72VPzMSk2A=="); + + UploadPartResponse uploadPartResponse = ChecksumResponseParser.parseUploadResponse(response); + + assertThat(uploadPartResponse.eTag()).isEqualTo("\"test-etag\""); + assertThat(uploadPartResponse.md5()).isEqualTo("rL0Y20zC+Fzt72VPzMSk2A=="); + } + + @Test + public void testExtractHashesFromHeader() throws IOException { + HttpResponse response = + createHttpResponse(null, "crc32c=AAAAAA==,md5=rL0Y20zC+Fzt72VPzMSk2A=="); + Map hashes = ChecksumResponseParser.extractHashesFromHeader(response); + assertThat(hashes).containsEntry("crc32c", "AAAAAA=="); + assertThat(hashes).containsEntry("md5", "rL0Y20zC+Fzt72VPzMSk2A=="); + } + + @Test + public void testExtractHashesFromHeader_singleHash() throws IOException { + HttpResponse response = createHttpResponse(null, "crc32c=AAAAAA=="); + Map hashes = ChecksumResponseParser.extractHashesFromHeader(response); + assertThat(hashes).containsEntry("crc32c", "AAAAAA=="); + assertThat(hashes).doesNotContainKey("md5"); + } + + @Test + public void testExtractHashesFromHeader_unknownHash() throws IOException { + HttpResponse response = + createHttpResponse(null, "crc32c=AAAAAA==,sha256=rL0Y20zC+Fzt72VPzMSk2A=="); + Map hashes = ChecksumResponseParser.extractHashesFromHeader(response); + assertThat(hashes).containsEntry("crc32c", "AAAAAA=="); + assertThat(hashes).doesNotContainKey("sha256"); + } + + @Test + public void testExtractHashesFromHeader_nullHeader() throws IOException { + HttpResponse response = createHttpResponse(null, null); + Map hashes = ChecksumResponseParser.extractHashesFromHeader(response); + assertThat(hashes).isEmpty(); + } + + @Test + public void testExtractHashesFromHeader_emptyHeader() throws IOException { + HttpResponse response = createHttpResponse(null, ""); + Map hashes = ChecksumResponseParser.extractHashesFromHeader(response); + assertThat(hashes).isEmpty(); + } + + private HttpResponse createHttpResponse(String etag, String googHash) throws IOException { + MockLowLevelHttpResponse lowLevelResponse = new MockLowLevelHttpResponse(); + if (etag != null) { + lowLevelResponse.addHeader("ETag", etag); + } + if (googHash != null) { + lowLevelResponse.addHeader("x-goog-hash", googHash); + } + HttpTransport transport = + new MockHttpTransport.Builder().setLowLevelHttpResponse(lowLevelResponse).build(); + HttpRequest request = + transport.createRequestFactory().buildGetRequest(new GenericUrl("http://example.com")); + return request.execute(); + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITMultipartUploadHttpRequestManagerTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITMultipartUploadHttpRequestManagerTest.java new file mode 100644 index 0000000000..0b7acdbd47 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITMultipartUploadHttpRequestManagerTest.java @@ -0,0 +1,843 @@ +/* + * 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; + +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpResponseStatus.OK; +import static org.junit.Assert.assertThrows; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.google.api.client.http.HttpResponseException; +import com.google.cloud.NoCredentials; +import com.google.cloud.storage.FakeHttpServer.HttpRequestHandler; +import com.google.cloud.storage.it.runner.StorageITRunner; +import com.google.cloud.storage.it.runner.annotations.Backend; +import com.google.cloud.storage.it.runner.annotations.ParallelFriendly; +import com.google.cloud.storage.it.runner.annotations.SingleBackend; +import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.CompletedMultipartUpload; +import com.google.cloud.storage.multipartupload.model.CompletedPart; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.ListPartsRequest; +import com.google.cloud.storage.multipartupload.model.ListPartsResponse; +import com.google.cloud.storage.multipartupload.model.ObjectLockMode; +import com.google.cloud.storage.multipartupload.model.Part; +import com.google.cloud.storage.multipartupload.model.UploadPartRequest; +import com.google.cloud.storage.multipartupload.model.UploadPartResponse; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.hash.Hashing; +import io.grpc.netty.shaded.io.netty.buffer.ByteBuf; +import io.grpc.netty.shaded.io.netty.buffer.Unpooled; +import io.grpc.netty.shaded.io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.grpc.netty.shaded.io.netty.handler.codec.http.FullHttpRequest; +import io.grpc.netty.shaded.io.netty.handler.codec.http.FullHttpResponse; +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpResponseStatus; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Collections; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; + +@RunWith(StorageITRunner.class) +@SingleBackend(Backend.PROD) +@ParallelFriendly +public final class ITMultipartUploadHttpRequestManagerTest { + private static final XmlMapper xmlMapper; + + static { + xmlMapper = new XmlMapper(); + xmlMapper.registerModule(new JavaTimeModule()); + } + + private MultipartUploadHttpRequestManager multipartUploadHttpRequestManager; + @Rule public final TemporaryFolder temp = new TemporaryFolder(); + + @Before + public void setUp() throws Exception { + HttpStorageOptions httpStorageOptions = + HttpStorageOptions.newBuilder() + .setProjectId("test-project") + .setCredentials(NoCredentials.getInstance()) + .build(); + multipartUploadHttpRequestManager = + MultipartUploadHttpRequestManager.createFrom(httpStorageOptions); + } + + @Test + public void sendCreateMultipartUploadRequest_success() throws Exception { + HttpRequestHandler handler = + req -> { + CreateMultipartUploadResponse response = + CreateMultipartUploadResponse.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(response)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + CreateMultipartUploadRequest request = + CreateMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .contentType("application/octet-stream") + .build(); + + CreateMultipartUploadResponse response = + multipartUploadHttpRequestManager.sendCreateMultipartUploadRequest(endpoint, request); + + assertThat(response).isNotNull(); + assertThat(response.bucket()).isEqualTo("test-bucket"); + assertThat(response.key()).isEqualTo("test-key"); + assertThat(response.uploadId()).isEqualTo("test-upload-id"); + } + } + + @Test + public void sendCreateMultipartUploadRequest_error() throws Exception { + HttpRequestHandler handler = + req -> { + FullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), HttpResponseStatus.BAD_REQUEST); + resp.headers().set(CONTENT_TYPE, "text/plain; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + CreateMultipartUploadRequest request = + CreateMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .contentType("application/octet-stream") + .build(); + + assertThrows( + HttpResponseException.class, + () -> + multipartUploadHttpRequestManager.sendCreateMultipartUploadRequest( + endpoint, request)); + } + } + + @Test + public void sendCreateMultipartUploadRequest_withCannedAcl() throws Exception { + HttpRequestHandler handler = + req -> { + assertThat(req.headers().get("x-goog-acl")).isEqualTo("authenticated-read"); + CreateMultipartUploadResponse response = + CreateMultipartUploadResponse.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(response)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + CreateMultipartUploadRequest request = + CreateMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .contentType("application/octet-stream") + .cannedAcl(Storage.PredefinedAcl.AUTHENTICATED_READ) + .build(); + + multipartUploadHttpRequestManager.sendCreateMultipartUploadRequest(endpoint, request); + } + } + + @Test + public void sendCreateMultipartUploadRequest_withMetadata() throws Exception { + HttpRequestHandler handler = + req -> { + assertThat(req.headers().get("x-goog-meta-key1")).isEqualTo("value1"); + assertThat(req.headers().get("x-goog-meta-key2")).isEqualTo("value2"); + CreateMultipartUploadResponse response = + CreateMultipartUploadResponse.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(response)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + CreateMultipartUploadRequest request = + CreateMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .contentType("application/octet-stream") + .metadata(ImmutableMap.of("key1", "value1", "key2", "value2")) + .build(); + + multipartUploadHttpRequestManager.sendCreateMultipartUploadRequest(endpoint, request); + } + } + + @Test + public void sendCreateMultipartUploadRequest_withStorageClass() throws Exception { + HttpRequestHandler handler = + req -> { + assertThat(req.headers().get("x-goog-storage-class")).isEqualTo("ARCHIVE"); + CreateMultipartUploadResponse response = + CreateMultipartUploadResponse.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(response)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + CreateMultipartUploadRequest request = + CreateMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .contentType("application/octet-stream") + .storageClass(StorageClass.ARCHIVE) + .build(); + + multipartUploadHttpRequestManager.sendCreateMultipartUploadRequest(endpoint, request); + } + } + + @Test + public void sendCreateMultipartUploadRequest_withKmsKeyName() throws Exception { + HttpRequestHandler handler = + req -> { + assertThat(req.headers().get("x-goog-encryption-kms-key-name")) + .isEqualTo("projects/p/locations/l/keyRings/r/cryptoKeys/k"); + CreateMultipartUploadResponse response = + CreateMultipartUploadResponse.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(response)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + CreateMultipartUploadRequest request = + CreateMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .contentType("application/octet-stream") + .kmsKeyName("projects/p/locations/l/keyRings/r/cryptoKeys/k") + .build(); + + multipartUploadHttpRequestManager.sendCreateMultipartUploadRequest(endpoint, request); + } + } + + @Test + public void sendCreateMultipartUploadRequest_withObjectLockMode() throws Exception { + HttpRequestHandler handler = + req -> { + assertThat(req.headers().get("x-goog-object-lock-mode")).isEqualTo("GOVERNANCE"); + CreateMultipartUploadResponse response = + CreateMultipartUploadResponse.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(response)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + CreateMultipartUploadRequest request = + CreateMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .contentType("application/octet-stream") + .objectLockMode(ObjectLockMode.GOVERNANCE) + .build(); + + multipartUploadHttpRequestManager.sendCreateMultipartUploadRequest(endpoint, request); + } + } + + @Test + public void sendCreateMultipartUploadRequest_withObjectLockRetainUntilDate() throws Exception { + OffsetDateTime retainUtil = OffsetDateTime.of(2024, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + HttpRequestHandler handler = + req -> { + OffsetDateTime actual = + Utils.offsetDateTimeRfc3339Codec.decode( + req.headers().get("x-goog-object-lock-retain-until-date")); + assertThat(actual).isEqualTo(retainUtil); + CreateMultipartUploadResponse response = + CreateMultipartUploadResponse.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(response)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + CreateMultipartUploadRequest request = + CreateMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .contentType("application/octet-stream") + .objectLockRetainUntilDate(retainUtil) + .build(); + + multipartUploadHttpRequestManager.sendCreateMultipartUploadRequest(endpoint, request); + } + } + + @Test + public void sendCreateMultipartUploadRequest_withCustomTime() throws Exception { + OffsetDateTime customTime = OffsetDateTime.of(2024, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + HttpRequestHandler handler = + req -> { + OffsetDateTime actual = + Utils.offsetDateTimeRfc3339Codec.decode(req.headers().get("x-goog-custom-time")); + assertThat(actual).isEqualTo(customTime); + CreateMultipartUploadResponse response = + CreateMultipartUploadResponse.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(response)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + CreateMultipartUploadRequest request = + CreateMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .contentType("application/octet-stream") + .customTime(customTime) + .build(); + + multipartUploadHttpRequestManager.sendCreateMultipartUploadRequest(endpoint, request); + } + } + + @Test + public void sendListPartsRequest_success() throws Exception { + HttpRequestHandler handler = + req -> { + OffsetDateTime lastModified = OffsetDateTime.of(2024, 5, 8, 17, 50, 0, 0, ZoneOffset.UTC); + ListPartsResponse listPartsResponse = + ListPartsResponse.builder() + .setBucket("test-bucket") + .setKey("test-key") + .setUploadId("test-upload-id") + .setPartNumberMarker(0) + .setNextPartNumberMarker(1) + .setMaxParts(1) + .setIsTruncated(false) + .setParts( + Collections.singletonList( + Part.builder() + .partNumber(1) + .eTag("\"etag\"") + .size(123) + .lastModified(lastModified) + .build())) + .build(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(listPartsResponse)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + ListPartsRequest request = + ListPartsRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .maxParts(1) + .partNumberMarker(0) + .build(); + + ListPartsResponse response = + multipartUploadHttpRequestManager.sendListPartsRequest(endpoint, request); + + assertThat(response).isNotNull(); + assertThat(response.getBucket()).isEqualTo("test-bucket"); + assertThat(response.getKey()).isEqualTo("test-key"); + assertThat(response.getUploadId()).isEqualTo("test-upload-id"); + assertThat(response.getPartNumberMarker()).isEqualTo(0); + assertThat(response.getNextPartNumberMarker()).isEqualTo(1); + assertThat(response.getMaxParts()).isEqualTo(1); + assertThat(response.isTruncated()).isFalse(); + assertThat(response.getParts()).hasSize(1); + Part part = response.getParts().get(0); + assertThat(part.partNumber()).isEqualTo(1); + assertThat(part.eTag()).isEqualTo("\"etag\""); + assertThat(part.size()).isEqualTo(123); + assertThat(part.lastModified()) + .isEqualTo(OffsetDateTime.of(2024, 5, 8, 17, 50, 0, 0, ZoneOffset.UTC)); + } + } + + @Test + public void sendListPartsRequest_bucketNotFound() throws Exception { + HttpRequestHandler handler = + req -> + new DefaultFullHttpResponse( + req.protocolVersion(), + HttpResponseStatus.NOT_FOUND, + Unpooled.wrappedBuffer("Bucket not found".getBytes(StandardCharsets.UTF_8))); + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + ListPartsRequest request = + ListPartsRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + + assertThrows( + HttpResponseException.class, + () -> multipartUploadHttpRequestManager.sendListPartsRequest(endpoint, request)); + } + } + + @Test + public void sendListPartsRequest_keyNotFound() throws Exception { + HttpRequestHandler handler = + req -> + new DefaultFullHttpResponse( + req.protocolVersion(), + HttpResponseStatus.NOT_FOUND, + Unpooled.wrappedBuffer("Key not found".getBytes(StandardCharsets.UTF_8))); + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + ListPartsRequest request = + ListPartsRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + + assertThrows( + HttpResponseException.class, + () -> multipartUploadHttpRequestManager.sendListPartsRequest(endpoint, request)); + } + } + + @Test + public void sendListPartsRequest_badRequest() throws Exception { + HttpRequestHandler handler = + req -> + new DefaultFullHttpResponse( + req.protocolVersion(), + HttpResponseStatus.BAD_REQUEST, + Unpooled.wrappedBuffer("Invalid uploadId".getBytes(StandardCharsets.UTF_8))); + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + ListPartsRequest request = + ListPartsRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("invalid-upload-id") + .build(); + + assertThrows( + HttpResponseException.class, + () -> multipartUploadHttpRequestManager.sendListPartsRequest(endpoint, request)); + } + } + + @Test + public void sendListPartsRequest_errorResponse() throws Exception { + HttpRequestHandler handler = + req -> { + FullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), HttpResponseStatus.BAD_REQUEST); + resp.headers().set(CONTENT_TYPE, "text/plain; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + ListPartsRequest request = + ListPartsRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + + assertThrows( + HttpResponseException.class, + () -> multipartUploadHttpRequestManager.sendListPartsRequest(endpoint, request)); + } + } + + @Test + public void sendAbortMultipartUploadRequest_success() throws Exception { + HttpRequestHandler handler = + req -> { + assertThat(req.uri()).contains("?uploadId=test-upload-id"); + AbortMultipartUploadResponse response = new AbortMultipartUploadResponse(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(response)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + AbortMultipartUploadRequest request = + AbortMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + + AbortMultipartUploadResponse response = + multipartUploadHttpRequestManager.sendAbortMultipartUploadRequest(endpoint, request); + + assertThat(response).isNotNull(); + } + } + + @Test + public void sendAbortMultipartUploadRequest_error() throws Exception { + HttpRequestHandler handler = + req -> { + FullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), HttpResponseStatus.BAD_REQUEST); + resp.headers().set(CONTENT_TYPE, "text/plain; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + AbortMultipartUploadRequest request = + AbortMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + + assertThrows( + HttpResponseException.class, + () -> + multipartUploadHttpRequestManager.sendAbortMultipartUploadRequest(endpoint, request)); + } + } + + @Test + public void sendCompleteMultipartUploadRequest_success() throws Exception { + HttpRequestHandler handler = + req -> { + CompleteMultipartUploadResponse response = + CompleteMultipartUploadResponse.builder() + .bucket("test-bucket") + .key("test-key") + .etag("\"test-etag\"") + .build(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(response)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + CompleteMultipartUploadRequest request = + CompleteMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .multipartUpload( + CompletedMultipartUpload.builder() + .parts( + ImmutableList.of( + CompletedPart.builder().partNumber(1).eTag("\"etag1\"").build(), + CompletedPart.builder().partNumber(2).eTag("\"etag2\"").build())) + .build()) + .build(); + + CompleteMultipartUploadResponse response = + multipartUploadHttpRequestManager.sendCompleteMultipartUploadRequest(endpoint, request); + + assertThat(response).isNotNull(); + assertThat(response.bucket()).isEqualTo("test-bucket"); + assertThat(response.key()).isEqualTo("test-key"); + assertThat(response.etag()).isEqualTo("\"test-etag\""); + } + } + + @Test + public void sendCompleteMultipartUploadRequest_error() throws Exception { + HttpRequestHandler handler = + req -> { + FullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), HttpResponseStatus.BAD_REQUEST); + resp.headers().set(CONTENT_TYPE, "text/plain; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + CompleteMultipartUploadRequest request = + CompleteMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .multipartUpload( + CompletedMultipartUpload.builder() + .parts( + ImmutableList.of( + CompletedPart.builder().partNumber(1).eTag("\"etag1\"").build(), + CompletedPart.builder().partNumber(2).eTag("\"etag2\"").build())) + .build()) + .build(); + + assertThrows( + HttpResponseException.class, + () -> + multipartUploadHttpRequestManager.sendCompleteMultipartUploadRequest( + endpoint, request)); + } + } + + @Test + public void sendCompleteMultipartUploadRequest_body() throws Exception { + HttpRequestHandler handler = + req -> { + FullHttpRequest fullHttpRequest = (FullHttpRequest) req; + ByteBuf content = fullHttpRequest.content(); + String body = content.toString(StandardCharsets.UTF_8); + assertThat(body) + .isEqualTo( + "1\"etag1\"2\"etag2\""); + CompleteMultipartUploadResponse response = + CompleteMultipartUploadResponse.builder() + .bucket("test-bucket") + .key("test-key") + .etag("\"test-etag\"") + .build(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(response)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + CompleteMultipartUploadRequest request = + CompleteMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .multipartUpload( + CompletedMultipartUpload.builder() + .parts( + ImmutableList.of( + CompletedPart.builder().partNumber(1).eTag("\"etag1\"").build(), + CompletedPart.builder().partNumber(2).eTag("\"etag2\"").build())) + .build()) + .build(); + + multipartUploadHttpRequestManager.sendCompleteMultipartUploadRequest(endpoint, request); + } + } + + @Test + public void sendUploadPartRequest_success() throws Exception { + String etag = "\"af1ed31420542285653c803a34aa839a\""; + String content = "hello world"; + byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8); + + HttpRequestHandler handler = + req -> { + assertThat(req.uri()).contains("?partNumber=1&uploadId=test-upload-id"); + FullHttpRequest fullReq = (FullHttpRequest) req; + ByteBuf requestContent = fullReq.content(); + byte[] receivedBytes = new byte[requestContent.readableBytes()]; + requestContent.readBytes(receivedBytes); + assertThat(receivedBytes).isEqualTo(contentBytes); + + DefaultFullHttpResponse resp = new DefaultFullHttpResponse(req.protocolVersion(), OK); + resp.headers().set("ETag", etag); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + UploadPartRequest request = + UploadPartRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .partNumber(1) + .build(); + + UploadPartResponse response = + multipartUploadHttpRequestManager.sendUploadPartRequest( + endpoint, request, RewindableContent.of(ByteBuffer.wrap(contentBytes))); + + assertThat(response).isNotNull(); + assertThat(response.eTag()).isEqualTo(etag); + } + } + + @Test + public void sendUploadPartRequest_withChecksums() throws Exception { + String etag = "\"af1ed31420542285653c803a34aa839a\""; + String content = "hello world"; + byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8); + String md5 = Hashing.md5().hashBytes(contentBytes).toString(); + String crc32c = "yZRlqg=="; + + HttpRequestHandler handler = + req -> { + assertThat(req.uri()).contains("?partNumber=1&uploadId=test-upload-id"); + assertThat(req.headers().get("x-goog-hash")).contains("crc32c=" + crc32c); + FullHttpRequest fullReq = (FullHttpRequest) req; + ByteBuf requestContent = fullReq.content(); + byte[] receivedBytes = new byte[requestContent.readableBytes()]; + requestContent.readBytes(receivedBytes); + assertThat(receivedBytes).isEqualTo(contentBytes); + + DefaultFullHttpResponse resp = new DefaultFullHttpResponse(req.protocolVersion(), OK); + resp.headers().set("ETag", etag); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + UploadPartRequest request = + UploadPartRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .partNumber(1) + .build(); + + UploadPartResponse response = + multipartUploadHttpRequestManager.sendUploadPartRequest( + endpoint, request, RewindableContent.of(ByteBuffer.wrap(contentBytes))); + + assertThat(response).isNotNull(); + assertThat(response.eTag()).isEqualTo(etag); + } + } + + @Test + public void sendUploadPartRequest_error() throws Exception { + HttpRequestHandler handler = + req -> { + FullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), HttpResponseStatus.BAD_REQUEST); + resp.headers().set(CONTENT_TYPE, "text/plain; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = URI.create(fakeHttpServer.getEndpoint() + "/"); + UploadPartRequest request = + UploadPartRequest.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .partNumber(1) + .build(); + + assertThrows( + HttpResponseException.class, + () -> + multipartUploadHttpRequestManager.sendUploadPartRequest( + endpoint, request, RewindableContent.empty())); + } + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/XmlObjectParserTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/XmlObjectParserTest.java new file mode 100644 index 0000000000..c2c146b5fd --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/XmlObjectParserTest.java @@ -0,0 +1,181 @@ +/* + * 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; + +import static com.google.common.truth.Truth.assertThat; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.google.cloud.storage.multipartupload.model.ListPartsResponse; +import com.google.common.base.MoreObjects; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import org.junit.Before; +import org.junit.Test; + +public class XmlObjectParserTest { + + private XmlObjectParser xmlObjectParser; + + @Before + public void setUp() { + xmlObjectParser = new XmlObjectParser(new XmlMapper()); + } + + @Test + public void testParseStringValueEnum() throws IOException { + // language=xml + String xml = + "\n" + " STANDARD" + ""; + InputStream in = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); + TestXmlObject2 expected = new TestXmlObject2(StorageClass.STANDARD); + TestXmlObject2 actual = + xmlObjectParser.parseAndClose(in, StandardCharsets.UTF_8, TestXmlObject2.class); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testParseDoesNotFailOnUnknownFields() throws IOException { + // language=xml + String xml = + "\n" + + " STANDARD" + + " blah" + + ""; + InputStream in = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); + TestXmlObject2 expected = new TestXmlObject2(StorageClass.STANDARD); + TestXmlObject2 actual = + xmlObjectParser.parseAndClose(in, StandardCharsets.UTF_8, TestXmlObject2.class); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testNestedParseStringValueEnum_undefined() throws IOException { + // language=xml + String xml = + "\n" + + " false\n" + + " bucket\n" + + " key\n" + + " \n" + + " 0\n" + + " 0\n" + + " 0\n" + + " false\n" + + " \n" + + " 1\n" + + " etag\n" + + " 33\n" + + " \n" + + " \n" + + ""; + ListPartsResponse listPartsResponse = + xmlObjectParser.parseAndClose(new StringReader(xml), ListPartsResponse.class); + assertThat(listPartsResponse.getStorageClass()).isNull(); + } + + @Test + public void testNestedParseStringValueEnum_null() throws IOException { + // language=xml + String xml = + "\n" + + " false\n" + + " bucket\n" + + " key\n" + + " \n" + + " 0\n" + + " 0\n" + + " 0\n" + + " false\n" + + " " + + " \n" + + " 1\n" + + " etag\n" + + " 33\n" + + " \n" + + " \n" + + ""; + ListPartsResponse listPartsResponse = + xmlObjectParser.parseAndClose(new StringReader(xml), ListPartsResponse.class); + assertThat(listPartsResponse.getStorageClass()).isNull(); + } + + @Test + public void testNestedParseStringValueEnum_nonNull() throws IOException { + // language=xml + String xml = + "\n" + + " false\n" + + " bucket\n" + + " key\n" + + " \n" + + " 0\n" + + " 0\n" + + " 0\n" + + " false\n" + + " STANDARD" + + " \n" + + " 1\n" + + " etag\n" + + " 33\n" + + " \n" + + " \n" + + ""; + ListPartsResponse listPartsResponse = + xmlObjectParser.parseAndClose(new StringReader(xml), ListPartsResponse.class); + assertThat(listPartsResponse.getStorageClass()).isEqualTo(StorageClass.STANDARD); + } + + private static class TestXmlObject {} + + private static final class TestXmlObject2 { + @JacksonXmlProperty(localName = "storageClass") + private StorageClass storageClass; + + private TestXmlObject2() {} + + public TestXmlObject2(StorageClass storageClass) { + this.storageClass = storageClass; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TestXmlObject2)) { + return false; + } + TestXmlObject2 that = (TestXmlObject2) o; + return Objects.equals(storageClass, that.storageClass); + } + + @Override + public int hashCode() { + return Objects.hashCode(storageClass); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("storageClass", storageClass).toString(); + } + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITMultipartUploadClientTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITMultipartUploadClientTest.java new file mode 100644 index 0000000000..526b5533e6 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITMultipartUploadClientTest.java @@ -0,0 +1,418 @@ +/* + * Copyright 2024 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.it; + +import static com.google.cloud.storage.TestUtils.xxd; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.cloud.ReadChannel; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.BucketInfo; +import com.google.cloud.storage.DataGenerator; +import com.google.cloud.storage.HttpStorageOptions; +import com.google.cloud.storage.MultipartUploadClient; +import com.google.cloud.storage.MultipartUploadSettings; +import com.google.cloud.storage.RequestBody; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageException; +import com.google.cloud.storage.TransportCompatibility.Transport; +import com.google.cloud.storage.it.runner.StorageITRunner; +import com.google.cloud.storage.it.runner.annotations.Backend; +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.multipartupload.model.AbortMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CompletedMultipartUpload; +import com.google.cloud.storage.multipartupload.model.CompletedPart; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.ListPartsRequest; +import com.google.cloud.storage.multipartupload.model.ListPartsResponse; +import com.google.cloud.storage.multipartupload.model.UploadPartRequest; +import com.google.cloud.storage.multipartupload.model.UploadPartResponse; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(StorageITRunner.class) +@CrossRun( + transports = {Transport.HTTP}, + backends = {Backend.PROD}) +public final class ITMultipartUploadClientTest { + + private static final int _5MiB = 5 * 1024 * 1024; + + @Inject public BucketInfo bucket; + + @Inject public Storage injectedStorage; + + @Inject public Transport transport; + + @Inject public Generator generator; + + private MultipartUploadClient multipartUploadClient; + + @Before + public void setUp() { + multipartUploadClient = + MultipartUploadClient.create( + MultipartUploadSettings.of((HttpStorageOptions) injectedStorage.getOptions())); + } + + @Test + public void testMultipartUpload() throws IOException { + doTest(12 * _5MiB + 37); + } + + @Test + public void testMultipartUpload_parallel() throws Exception { + // This test is slow and resource-intensive. + long objectSize = 513 * 1024 * 1024 + 29; // 513 MiB + 29 bytes + int partSize = 8 * 1024 * 1024; // 8 MiB + + Path tempFile = Files.createTempFile("multipart-upload-it", ".bin"); + try { + createRandomFile(tempFile, objectSize); + + BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); + CreateMultipartUploadResponse createResponse = createMultipartUpload(info); + String uploadId = createResponse.uploadId(); + + List completedParts = + parallelUpload(info, uploadId, tempFile, objectSize, partSize); + + completeMultipartUpload(info, uploadId, completedParts); + + Blob result = injectedStorage.get(info.getBlobId()); + assertThat(result).isNotNull(); + assertThat(result.getSize()).isEqualTo(objectSize); + + verifyContents(info, tempFile); + } finally { + Files.deleteIfExists(tempFile); + } + } + + @Test + public void testAbort() { + BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); + + CreateMultipartUploadResponse createResponse = createMultipartUpload(info); + String uploadId = createResponse.uploadId(); + + byte[] bytes = DataGenerator.rand(new Random()).genBytes(_5MiB); + + uploadPart(info, uploadId, 1, bytes); + abortMultipartUpload(info, uploadId); + + Blob blob = injectedStorage.get(info.getBlobId()); + assertThat(blob).isNull(); + } + + @Test + public void testMultipartUpload_singlePart() throws IOException { + doTest(_5MiB - 1); + } + + @Test + public void testMultipartUpload_zeroByteFile() { + BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); + + CreateMultipartUploadResponse createResponse = createMultipartUpload(info); + String uploadId = createResponse.uploadId(); + byte[] bytes = new byte[0]; + + UploadPartResponse uploadPartResponse = uploadPart(info, uploadId, 1, bytes); + + List completedParts = new ArrayList<>(); + completedParts.add( + CompletedPart.builder().partNumber(1).eTag(uploadPartResponse.eTag()).build()); + + completeMultipartUpload(info, uploadId, completedParts); + + Blob result = injectedStorage.get(info.getBlobId()); + byte[] actual = injectedStorage.readAllBytes(info.getBlobId()); + + assertThat(result).isNotNull(); + assertThat(result.getSize()).isEqualTo(0); + assertBytesEqual(actual, new byte[0]); + } + + @Test + public void testComplete_noParts() { + BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); + CreateMultipartUploadResponse createResponse = createMultipartUpload(info); + String uploadId = createResponse.uploadId(); + + List completedParts = new ArrayList<>(); + try { + completeMultipartUpload(info, uploadId, completedParts); + fail("Expected StorageException"); + } catch (StorageException e) { + assertThat(e.getMessage()).contains("MalformedCompleteMultipartUploadRequest"); + } finally { + // cleanup + abortMultipartUpload(info, uploadId); + } + } + + @Test + public void testListParts_afterAbort() { + BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); + + CreateMultipartUploadResponse createResponse = createMultipartUpload(info); + String uploadId = createResponse.uploadId(); + + byte[] bytes = DataGenerator.rand(new Random()).genBytes(_5MiB); + uploadPart(info, uploadId, 1, bytes); + + abortMultipartUpload(info, uploadId); + + try { + ListPartsRequest.Builder listPartsBuilder = + ListPartsRequest.builder() + .bucket(info.getBucket()) + .key(info.getName()) + .uploadId(uploadId); + multipartUploadClient.listParts(listPartsBuilder.build()); + fail("Expected StorageException"); + } catch (StorageException e) { + assertThat(e.getMessage()).contains("The requested upload was not found."); + } + } + + @Test + public void testComplete_wrongETag() { + BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); + CreateMultipartUploadResponse createResponse = createMultipartUpload(info); + String uploadId = createResponse.uploadId(); + + Random rand = new Random(); + byte[] bytes1 = DataGenerator.rand(rand).genBytes(_5MiB); + UploadPartResponse uploadPartResponse1 = uploadPart(info, uploadId, 1, bytes1); + + byte[] bytes2 = DataGenerator.rand(rand).genBytes(_5MiB); + uploadPart(info, uploadId, 2, bytes2); + + List completedParts = new ArrayList<>(); + completedParts.add( + CompletedPart.builder().partNumber(1).eTag(uploadPartResponse1.eTag()).build()); + completedParts.add( + CompletedPart.builder().partNumber(2).eTag("\"dummytag\"").build()); // wrong etag + + try { + completeMultipartUpload(info, uploadId, completedParts); + fail("Expected StorageException"); + } catch (StorageException e) { + assertThat(e.getMessage()).contains("The requested upload part was not found."); + } finally { + abortMultipartUpload(info, uploadId); + } + } + + private void doTest(int objectSizeBytes) throws IOException { + BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); + + CreateMultipartUploadResponse createResponse = createMultipartUpload(info); + String uploadId = createResponse.uploadId(); + byte[] bytes = DataGenerator.rand(new Random()).genBytes(objectSizeBytes); + + List completedParts = new ArrayList<>(); + int partNumber = 1; + for (int i = 0; i < objectSizeBytes; i += _5MiB) { + int len = Math.min(_5MiB, objectSizeBytes - i); + byte[] partBuffer = java.util.Arrays.copyOfRange(bytes, i, i + len); + UploadPartResponse uploadPartResponse = uploadPart(info, uploadId, partNumber, partBuffer); + completedParts.add( + CompletedPart.builder().partNumber(partNumber).eTag(uploadPartResponse.eTag()).build()); + partNumber++; + } + completedParts.sort(Comparator.comparingInt(CompletedPart::partNumber)); + + ListPartsRequest.Builder listPartsBuilder = + ListPartsRequest.builder().bucket(info.getBucket()).key(info.getName()).uploadId(uploadId); + ListPartsResponse listPartsResponse = multipartUploadClient.listParts(listPartsBuilder.build()); + assertThat(listPartsResponse.getParts()).hasSize(completedParts.size()); + + completeMultipartUpload(info, uploadId, completedParts); + + Blob result = injectedStorage.get(info.getBlobId()); + byte[] actual = injectedStorage.readAllBytes(info.getBlobId()); + + assertThat(result).isNotNull(); + assertBytesEqual(actual, bytes); + } + + private void assertBytesEqual(byte[] actual, byte[] expected) { + assertThat(actual).isEqualTo(expected); + assertThat(xxd(actual)).isEqualTo(xxd(expected)); + } + + private CreateMultipartUploadResponse createMultipartUpload(BlobInfo info) { + CreateMultipartUploadRequest createRequest = + CreateMultipartUploadRequest.builder().bucket(info.getBucket()).key(info.getName()).build(); + return multipartUploadClient.createMultipartUpload(createRequest); + } + + private UploadPartResponse uploadPart( + BlobInfo info, String uploadId, int partNumber, byte[] bytes) { + RequestBody body = RequestBody.of(ByteBuffer.wrap(bytes)); + return uploadPart(info, uploadId, partNumber, body); + } + + private UploadPartResponse uploadPart( + BlobInfo info, String uploadId, int partNumber, RequestBody body) { + UploadPartRequest uploadPartRequest = + UploadPartRequest.builder() + .partNumber(partNumber) + .uploadId(uploadId) + .bucket(info.getBucket()) + .key(info.getName()) + .build(); + return multipartUploadClient.uploadPart(uploadPartRequest, body); + } + + private void completeMultipartUpload(BlobInfo info, String uploadId, List parts) { + CompletedMultipartUpload completedMultipartUpload = + CompletedMultipartUpload.builder().parts(parts).build(); + CompleteMultipartUploadRequest completeRequest = + CompleteMultipartUploadRequest.builder() + .bucket(info.getBucket()) + .key(info.getName()) + .uploadId(uploadId) + .multipartUpload(completedMultipartUpload) + .build(); + multipartUploadClient.completeMultipartUpload(completeRequest); + } + + private void abortMultipartUpload(BlobInfo info, String uploadId) { + AbortMultipartUploadRequest abortRequest = + AbortMultipartUploadRequest.builder() + .bucket(info.getBucket()) + .key(info.getName()) + .uploadId(uploadId) + .build(); + multipartUploadClient.abortMultipartUpload(abortRequest); + } + + private void createRandomFile(Path path, long size) throws IOException { + try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(path))) { + byte[] buffer = new byte[1024 * 1024]; // 1MB buffer + Random random = new Random(); + for (long i = 0; i < size; i += buffer.length) { + random.nextBytes(buffer); + int len = (int) Math.min(buffer.length, size - i); + os.write(buffer, 0, len); + } + } + } + + private List parallelUpload( + BlobInfo info, String uploadId, Path localFile, long objectSize, int partSize) + throws ExecutionException, InterruptedException { + int numThreads = Runtime.getRuntime().availableProcessors(); + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + List> futures = new ArrayList<>(); + + long numParts = (objectSize + partSize - 1) / partSize; + + for (int i = 0; i < numParts; i++) { + final int partNumber = i + 1; + final long offset = (long) i * partSize; + final long len = Math.min(partSize, objectSize - offset); + + Callable uploadTask = + () -> { + ByteBuffer partBuffer = ByteBuffer.allocate((int) len); + try (FileChannel fileChannel = FileChannel.open(localFile, StandardOpenOption.READ)) { + fileChannel.read(partBuffer, offset); + } + partBuffer.flip(); + RequestBody partBody = RequestBody.of(partBuffer); + UploadPartResponse uploadPartResponse = + uploadPart(info, uploadId, partNumber, partBody); + return CompletedPart.builder() + .partNumber(partNumber) + .eTag(uploadPartResponse.eTag()) + .build(); + }; + futures.add(executor.submit(uploadTask)); + } + + List completedParts = new ArrayList<>(); + for (Future future : futures) { + completedParts.add(future.get()); + } + executor.shutdown(); + + completedParts.sort(Comparator.comparingInt(CompletedPart::partNumber)); + return completedParts; + } + + private void verifyContents(BlobInfo info, Path expectedFile) throws IOException { + try (ReadChannel reader = injectedStorage.reader(info.getBlobId()); + InputStream expectedStream = new BufferedInputStream(Files.newInputStream(expectedFile))) { + + ByteBuffer cloudBuffer = ByteBuffer.allocate(1024 * 1024); // 1MB buffer + + while (reader.read(cloudBuffer) > 0) { + cloudBuffer.flip(); + + byte[] actualBytes = new byte[cloudBuffer.remaining()]; + cloudBuffer.get(actualBytes); + + byte[] expectedBytes = new byte[actualBytes.length]; + int bytesRead = 0; + while (bytesRead < expectedBytes.length) { + int readResult = + expectedStream.read(expectedBytes, bytesRead, expectedBytes.length - bytesRead); + if (readResult == -1) { + break; + } + bytesRead += readResult; + } + + assertThat(bytesRead).isEqualTo(expectedBytes.length); + assertBytesEqual(actualBytes, expectedBytes); + cloudBuffer.clear(); + } + assertThat(expectedStream.read()).isEqualTo(-1); // Ensure we have read the whole local file + } + } +} From feb191214e1551af35a5c391196e296ab4ce5372 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 3 Nov 2025 23:55:45 +0000 Subject: [PATCH 11/14] build(deps): update googleapis/sdk-platform-java action to v2.64.0 (#3380) --- .github/workflows/hermetic_library_generation.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hermetic_library_generation.yaml b/.github/workflows/hermetic_library_generation.yaml index 9872c68489..76c6ec9ee6 100644 --- a/.github/workflows/hermetic_library_generation.yaml +++ b/.github/workflows/hermetic_library_generation.yaml @@ -43,7 +43,7 @@ jobs: with: fetch-depth: 0 token: ${{ secrets.CLOUD_JAVA_BOT_TOKEN }} - - uses: googleapis/sdk-platform-java/.github/scripts@v2.63.0 + - uses: googleapis/sdk-platform-java/.github/scripts@v2.64.0 if: env.SHOULD_RUN == 'true' with: base_ref: ${{ github.base_ref }} From ac3be4b7e82d9340ede7d527a26ffe3e2ba58909 Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Tue, 4 Nov 2025 15:45:55 -0500 Subject: [PATCH 12/14] fix: call response.disconnect() after resolving resumable upload url (#3385) --- .../com/google/cloud/storage/spi/v1/HttpStorageRpc.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 ca11f96673..20650a11d0 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 @@ -1156,7 +1156,9 @@ public String open(StorageObject object, Map options) { if (response.getStatusCode() != 200) { throw buildStorageException(response.getStatusCode(), response.getStatusMessage()); } - return response.getHeaders().getLocation(); + String location = response.getHeaders().getLocation(); + response.disconnect(); + return location; } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); @@ -1190,7 +1192,9 @@ public String open(String signedURL) { if (response.getStatusCode() != 201) { throw buildStorageException(response.getStatusCode(), response.getStatusMessage()); } - return response.getHeaders().getLocation(); + String location = response.getHeaders().getLocation(); + response.disconnect(); + return location; } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); From e3d3700e06de2b0113e1cb01e99ef4aeed3c62c9 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 7 Nov 2025 22:00:18 +0000 Subject: [PATCH 13/14] deps: update dependency com.google.cloud:sdk-platform-java-config to v3.54.1 (#3381) Co-authored-by: BenWhitehead --- .../workflows/unmanaged_dependency_check.yaml | 2 +- google-cloud-storage-bom/pom.xml | 2 +- .../GenerateGrpcProtobufReflectConfig.java | 3 +- .../google/cloud/storage/reflect-config.json | 55 ++++++++++++++++++- pom.xml | 2 +- 5 files changed, 56 insertions(+), 8 deletions(-) diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml index 756c7dfc75..d8267b1548 100644 --- a/.github/workflows/unmanaged_dependency_check.yaml +++ b/.github/workflows/unmanaged_dependency_check.yaml @@ -17,6 +17,6 @@ jobs: # repository .kokoro/build.sh - name: Unmanaged dependency check - uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.53.0 + uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.54.1 with: bom-path: google-cloud-storage-bom/pom.xml diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml index f11503a51c..73ed033a1e 100644 --- a/google-cloud-storage-bom/pom.xml +++ b/google-cloud-storage-bom/pom.xml @@ -24,7 +24,7 @@ com.google.cloud sdk-platform-java-config - 3.53.0 + 3.54.1 diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/GenerateGrpcProtobufReflectConfig.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/GenerateGrpcProtobufReflectConfig.java index 0d2b3b41a8..35d5bd888b 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/GenerateGrpcProtobufReflectConfig.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/GenerateGrpcProtobufReflectConfig.java @@ -17,7 +17,6 @@ package com.google.cloud.storage; import com.google.protobuf.AbstractMessage; -import com.google.protobuf.Message; import com.google.protobuf.ProtocolMessageEnum; import io.github.classgraph.ClassGraph; import io.github.classgraph.ClassInfo; @@ -57,7 +56,7 @@ public static void main(String[] args) throws IOException { + " \"methods\":[{\"name\":\"\",\"parameterTypes\":[] }]\n" + " }"), Stream.of( - scanResult.getSubclasses(Message.class).stream(), + scanResult.getSubclasses(AbstractMessage.class).stream(), scanResult.getSubclasses(AbstractMessage.Builder.class).stream(), scanResult .getAllEnums() diff --git a/google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/reflect-config.json b/google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/reflect-config.json index 138caa1456..500a610c11 100644 --- a/google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/reflect-config.json +++ b/google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/reflect-config.json @@ -15,7 +15,8 @@ "allDeclaredMethods":true, "methods":[{"name":"","parameterTypes":[] }] }, - { "name": "com.google.protobuf.Message$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "com.google.protobuf.GeneratedMessageV3", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "com.google.protobuf.GeneratedMessageV3$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.alts.internal.AltsContext", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.alts.internal.AltsContext$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.alts.internal.Endpoint", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -222,6 +223,8 @@ { "name": "io.grpc.xds.shaded.com.github.udpa.udpa.type.v1.TypedStruct$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.com.github.xds.core.v3.Authority", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.com.github.xds.core.v3.Authority$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.com.github.xds.core.v3.CidrRange", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.com.github.xds.core.v3.CidrRange$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.com.github.xds.core.v3.CollectionEntry", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.com.github.xds.core.v3.CollectionEntry$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.com.github.xds.core.v3.CollectionEntry$InlineEntry", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -698,6 +701,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.KeyValueAppend$KeyValueAppendAction", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.KeyValueMutation", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.KeyValueMutation$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.KeyValuePair", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.KeyValuePair$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.Locality", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.Locality$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.Metadata", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -706,6 +711,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.Node$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.PathConfigSource", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.PathConfigSource$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.PerHostConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.PerHostConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.Pipe", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.Pipe$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.ProxyProtocolConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -749,9 +756,17 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketAddress", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketAddress$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketAddress$Protocol", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketCmsgHeaders", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketCmsgHeaders$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketOption", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketOption$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketOption$SocketState", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketOption$SocketType", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketOption$SocketType$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketOption$SocketType$Datagram", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketOption$SocketType$Datagram$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketOption$SocketType$Stream", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketOption$SocketType$Stream$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketOptionsOverride", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SocketOptionsOverride$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.SubstitutionFormatString", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -760,6 +775,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.TcpKeepalive$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.TcpProtocolOptions", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.TcpProtocolOptions$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.TlvEntry", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.TlvEntry$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.TrafficDirection", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.TransportSocket", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3.TransportSocket$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -791,12 +808,16 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.EndpointLoadMetricStats$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.LbEndpointCollection", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.LbEndpointCollection$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.LedsClusterLocalityConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.LedsClusterLocalityConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints$LbEndpointList", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints$LbEndpointList$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.UnnamedEndpointLoadMetricStats", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.UnnamedEndpointLoadMetricStats$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.UpstreamEndpointStats", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.UpstreamEndpointStats$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -813,8 +834,6 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.Filter$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.FilterChain", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.FilterChain$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, - { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.FilterChain$OnDemandConfiguration", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, - { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.FilterChain$OnDemandConfiguration$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.FilterChainMatch", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.FilterChainMatch$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.FilterChainMatch$ConnectionSourceType", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -827,6 +846,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.Listener$DeprecatedV1", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.Listener$DeprecatedV1$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.Listener$DrainType", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.Listener$FcdsConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.Listener$FcdsConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.Listener$InternalListenerConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.Listener$InternalListenerConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3.ListenerCollection", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -884,6 +905,7 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.overload.v3.Trigger$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.rbac.v3.Action", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.rbac.v3.Action$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.rbac.v3.MetadataSource", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.rbac.v3.Permission", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.rbac.v3.Permission$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.rbac.v3.Permission$Set", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -904,6 +926,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.rbac.v3.RBAC$AuditLoggingOptions$AuditLoggerConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.rbac.v3.RBAC$AuditLoggingOptions$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.rbac.v3.RBAC$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.rbac.v3.SourcedMetadata", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.rbac.v3.SourcedMetadata$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.CorsPolicy", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -944,6 +968,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Action$MetaData$Source", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Action$QueryParameterValueMatch", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Action$QueryParameterValueMatch$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Action$QueryParameters", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Action$QueryParameters$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Action$RemoteAddress", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Action$RemoteAddress$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Action$RequestHeaders", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -951,6 +977,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Action$SourceCluster", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Action$SourceCluster$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$HitsAddend", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$HitsAddend$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Override", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Override$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.RateLimit$Override$DynamicMetadata", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -1032,6 +1060,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3.WeightedCluster$ClusterWeight$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.trace.v3.DatadogConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.trace.v3.DatadogConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.trace.v3.DatadogRemoteConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.trace.v3.DatadogRemoteConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.trace.v3.DynamicOtConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.trace.v3.DynamicOtConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.config.trace.v3.LightstepConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -1074,6 +1104,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.data.accesslog.v3.TLSProperties$CertificateProperties$SubjectAltName", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.data.accesslog.v3.TLSProperties$CertificateProperties$SubjectAltName$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.data.accesslog.v3.TLSProperties$TLSVersion", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.clusters.aggregate.v3.AggregateClusterResource", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.clusters.aggregate.v3.AggregateClusterResource$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.common.fault.v3.FaultDelay", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -1093,6 +1125,14 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort$HeaderAbort$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.fault.v3.HTTPFault", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.fault.v3.HTTPFault$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.Audience", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.Audience$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.TokenCacheConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.TokenCacheConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.TokenHeader", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.TokenHeader$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings$BucketIdBuilder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings$BucketIdBuilder$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -1176,6 +1216,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.common.v3.LocalityLbConfig$LocalityWeightedLbConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.common.v3.LocalityLbConfig$ZoneAwareLbConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.common.v3.LocalityLbConfig$ZoneAwareLbConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.common.v3.LocalityLbConfig$ZoneAwareLbConfig$ForceLocalZone", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.common.v3.LocalityLbConfig$ZoneAwareLbConfig$ForceLocalZone$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.common.v3.SlowStartConfig", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.common.v3.SlowStartConfig$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -1190,6 +1232,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.round_robin.v3.RoundRobin$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.wrr_locality.v3.WrrLocality", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.wrr_locality.v3.WrrLocality$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.http_11_proxy.v3.Http11ProxyUpstreamTransport", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.http_11_proxy.v3.Http11ProxyUpstreamTransport$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -1225,6 +1269,7 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsKeyLog$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsParameters", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsParameters$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsParameters$CompliancePolicy", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsParameters$TlsProtocol", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsSessionTicketKeys", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsSessionTicketKeys$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -1252,6 +1297,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.service.discovery.v3.Resource$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.service.discovery.v3.Resource$CacheControl", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.service.discovery.v3.Resource$CacheControl$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.service.discovery.v3.ResourceError", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.service.discovery.v3.ResourceError$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.service.discovery.v3.ResourceLocator", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.service.discovery.v3.ResourceLocator$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.service.discovery.v3.ResourceName", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, @@ -1294,6 +1341,8 @@ { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.type.http.v3.PathTransformation$Operation$MergeSlashes$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.type.http.v3.PathTransformation$Operation$NormalizePathRFC3986", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.type.http.v3.PathTransformation$Operation$NormalizePathRFC3986$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.type.matcher.v3.AddressMatcher", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, + { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.type.matcher.v3.AddressMatcher$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.type.matcher.v3.DoubleMatcher", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.type.matcher.v3.DoubleMatcher$Builder", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, { "name": "io.grpc.xds.shaded.io.envoyproxy.envoy.type.matcher.v3.FilterStateMatcher", "queryAllDeclaredConstructors": true, "queryAllPublicConstructors": true, "queryAllDeclaredMethods": true, "allPublicMethods": true, "allDeclaredClasses": true, "allPublicClasses": true }, diff --git a/pom.xml b/pom.xml index 593f316fd7..95e2aad296 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ com.google.cloud sdk-platform-java-config - 3.53.0 + 3.54.1 From b05814367c0211887a430a1165c6764f525fba9f Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 17:35:45 -0500 Subject: [PATCH 14/14] chore(main): release 2.60.0 (#3360) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: cloud-java-bot --- CHANGELOG.md | 24 +++++++++++++++++++ README.md | 6 ++--- 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 +++++------ 14 files changed, 69 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2766dc4e38..51558248ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [2.60.0](https://github.com/googleapis/java-storage/compare/v2.59.0...v2.60.0) (2025-11-07) + + +### Features + +* Add preview MultipartUploadClient#abortMultipartUpload https://github.com/googleapis/java-storage/pull/3361 ([160fa9a](https://github.com/googleapis/java-storage/commit/160fa9af7aa492373a9d9b40f65a6c56d7cab5ef)) +* Add preview MultipartUploadClient#completeMultipartUpload https://github.com/googleapis/java-storage/pull/3372 ([160fa9a](https://github.com/googleapis/java-storage/commit/160fa9af7aa492373a9d9b40f65a6c56d7cab5ef)) +* Add preview MultipartUploadClient#createMultipartUpload https://github.com/googleapis/java-storage/pull/3356 ([160fa9a](https://github.com/googleapis/java-storage/commit/160fa9af7aa492373a9d9b40f65a6c56d7cab5ef)) +* Add preview MultipartUploadClient#listParts https://github.com/googleapis/java-storage/pull/3359 ([160fa9a](https://github.com/googleapis/java-storage/commit/160fa9af7aa492373a9d9b40f65a6c56d7cab5ef)) +* Add preview MultipartUploadClient#uploadPart https://github.com/googleapis/java-storage/pull/3375 ([160fa9a](https://github.com/googleapis/java-storage/commit/160fa9af7aa492373a9d9b40f65a6c56d7cab5ef)) +* Add preview MultipartUploadSettings ([160fa9a](https://github.com/googleapis/java-storage/commit/160fa9af7aa492373a9d9b40f65a6c56d7cab5ef)) + + +### Bug Fixes + +* Add new system property (com.google.cloud.storage.grpc.bound_token) to allow disabling bound token use with grpc ([#3365](https://github.com/googleapis/java-storage/issues/3365)) ([ebf5e6d](https://github.com/googleapis/java-storage/commit/ebf5e6d30d8dc197ab388a70cc0d465c0f740496)) +* Call response.disconnect() after resolving resumable upload url ([#3385](https://github.com/googleapis/java-storage/issues/3385)) ([ac3be4b](https://github.com/googleapis/java-storage/commit/ac3be4b7e82d9340ede7d527a26ffe3e2ba58909)) +* **deps:** Update the Java code generator (gapic-generator-java) to 2.63.0 ([c1a8968](https://github.com/googleapis/java-storage/commit/c1a8968799c1cf5a970fe9f303adccdad0a117c8)) + + +### Dependencies + +* Update dependency com.google.cloud:sdk-platform-java-config to v3.54.1 ([#3381](https://github.com/googleapis/java-storage/issues/3381)) ([e3d3700](https://github.com/googleapis/java-storage/commit/e3d3700e06de2b0113e1cb01e99ef4aeed3c62c9)) + ## [2.59.0](https://github.com/googleapis/java-storage/compare/v2.58.1...v2.59.0) (2025-10-21) diff --git a/README.md b/README.md index 4fa6de6833..c6e229a2f1 100644 --- a/README.md +++ b/README.md @@ -66,13 +66,13 @@ implementation 'com.google.cloud:google-cloud-storage' If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-storage:2.59.0' +implementation 'com.google.cloud:google-cloud-storage:2.60.0' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "2.59.0" +libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "2.60.0" ``` ## Authentication @@ -484,7 +484,7 @@ Java is a registered trademark of Oracle and/or its affiliates. [kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-storage/java11.html [stability-image]: https://img.shields.io/badge/stability-stable-green [maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-storage.svg -[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-storage/2.59.0 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-storage/2.60.0 [authentication]: https://github.com/googleapis/google-cloud-java#authentication [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles diff --git a/gapic-google-cloud-storage-v2/pom.xml b/gapic-google-cloud-storage-v2/pom.xml index eb10795fd7..831491d148 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.59.1-SNAPSHOT + 2.60.0 gapic-google-cloud-storage-v2 GRPC library for gapic-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.59.1-SNAPSHOT + 2.60.0 diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml index 73ed033a1e..49f96d0569 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.59.1-SNAPSHOT + 2.60.0 pom com.google.cloud @@ -69,37 +69,37 @@ com.google.cloud google-cloud-storage - 2.59.1-SNAPSHOT + 2.60.0 com.google.api.grpc gapic-google-cloud-storage-v2 - 2.59.1-SNAPSHOT + 2.60.0 com.google.api.grpc grpc-google-cloud-storage-v2 - 2.59.1-SNAPSHOT + 2.60.0 com.google.api.grpc proto-google-cloud-storage-v2 - 2.59.1-SNAPSHOT + 2.60.0 com.google.cloud google-cloud-storage-control - 2.59.1-SNAPSHOT + 2.60.0 com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.59.1-SNAPSHOT + 2.60.0 com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.59.1-SNAPSHOT + 2.60.0 diff --git a/google-cloud-storage-control/pom.xml b/google-cloud-storage-control/pom.xml index d85264ab04..3124bce20c 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.59.1-SNAPSHOT + 2.60.0 google-cloud-storage-control GRPC library for google-cloud-storage-control com.google.cloud google-cloud-storage-parent - 2.59.1-SNAPSHOT + 2.60.0 diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 54492aff0b..69543226f4 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -2,7 +2,7 @@ 4.0.0 google-cloud-storage - 2.59.1-SNAPSHOT + 2.60.0 jar Google Cloud Storage https://github.com/googleapis/java-storage @@ -12,7 +12,7 @@ com.google.cloud google-cloud-storage-parent - 2.59.1-SNAPSHOT + 2.60.0 google-cloud-storage diff --git a/grpc-google-cloud-storage-control-v2/pom.xml b/grpc-google-cloud-storage-control-v2/pom.xml index 3e6ac29825..ad8de05040 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.59.1-SNAPSHOT + 2.60.0 grpc-google-cloud-storage-control-v2 GRPC library for google-cloud-storage com.google.cloud google-cloud-storage-parent - 2.59.1-SNAPSHOT + 2.60.0 diff --git a/grpc-google-cloud-storage-v2/pom.xml b/grpc-google-cloud-storage-v2/pom.xml index 8dd4c9e1e2..52ec9fb17c 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.59.1-SNAPSHOT + 2.60.0 grpc-google-cloud-storage-v2 GRPC library for grpc-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.59.1-SNAPSHOT + 2.60.0 diff --git a/pom.xml b/pom.xml index 95e2aad296..1cda67bd46 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-storage-parent pom - 2.59.1-SNAPSHOT + 2.60.0 Storage Parent https://github.com/googleapis/java-storage @@ -82,7 +82,7 @@ com.google.cloud google-cloud-storage - 2.59.1-SNAPSHOT + 2.60.0 com.google.apis @@ -104,32 +104,32 @@ com.google.api.grpc proto-google-cloud-storage-v2 - 2.59.1-SNAPSHOT + 2.60.0 com.google.api.grpc grpc-google-cloud-storage-v2 - 2.59.1-SNAPSHOT + 2.60.0 com.google.api.grpc gapic-google-cloud-storage-v2 - 2.59.1-SNAPSHOT + 2.60.0 com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.59.1-SNAPSHOT + 2.60.0 com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.59.1-SNAPSHOT + 2.60.0 com.google.cloud google-cloud-storage-control - 2.59.1-SNAPSHOT + 2.60.0 com.google.cloud diff --git a/proto-google-cloud-storage-control-v2/pom.xml b/proto-google-cloud-storage-control-v2/pom.xml index edf1e6e394..859ca38843 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.59.1-SNAPSHOT + 2.60.0 proto-google-cloud-storage-control-v2 Proto library for proto-google-cloud-storage-control-v2 com.google.cloud google-cloud-storage-parent - 2.59.1-SNAPSHOT + 2.60.0 diff --git a/proto-google-cloud-storage-v2/pom.xml b/proto-google-cloud-storage-v2/pom.xml index 44c2c2eb35..10182eded6 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.59.1-SNAPSHOT + 2.60.0 proto-google-cloud-storage-v2 PROTO library for proto-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.59.1-SNAPSHOT + 2.60.0 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 1b8a94203f..5a4fe45e1e 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,12 +28,12 @@ com.google.cloud google-cloud-storage - 2.59.1-SNAPSHOT + 2.60.0 com.google.cloud google-cloud-storage-control - 2.59.1-SNAPSHOT + 2.60.0 compile @@ -70,7 +70,7 @@ com.google.cloud google-cloud-storage - 2.59.1-SNAPSHOT + 2.60.0 tests test diff --git a/storage-shared-benchmarking/pom.xml b/storage-shared-benchmarking/pom.xml index ffd421ac14..e3b5df2053 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.59.1-SNAPSHOT + 2.60.0 @@ -31,7 +31,7 @@ com.google.cloud google-cloud-storage - 2.59.1-SNAPSHOT + 2.60.0 tests diff --git a/versions.txt b/versions.txt index 47e29cbca1..bc872205ab 100644 --- a/versions.txt +++ b/versions.txt @@ -1,10 +1,10 @@ # Format: # module:released-version:current-version -google-cloud-storage:2.59.0:2.59.1-SNAPSHOT -gapic-google-cloud-storage-v2:2.59.0:2.59.1-SNAPSHOT -grpc-google-cloud-storage-v2:2.59.0:2.59.1-SNAPSHOT -proto-google-cloud-storage-v2:2.59.0:2.59.1-SNAPSHOT -google-cloud-storage-control:2.59.0:2.59.1-SNAPSHOT -proto-google-cloud-storage-control-v2:2.59.0:2.59.1-SNAPSHOT -grpc-google-cloud-storage-control-v2:2.59.0:2.59.1-SNAPSHOT +google-cloud-storage:2.60.0:2.60.0 +gapic-google-cloud-storage-v2:2.60.0:2.60.0 +grpc-google-cloud-storage-v2:2.60.0:2.60.0 +proto-google-cloud-storage-v2:2.60.0:2.60.0 +google-cloud-storage-control:2.60.0:2.60.0 +proto-google-cloud-storage-control-v2:2.60.0:2.60.0 +grpc-google-cloud-storage-control-v2:2.60.0:2.60.0