From ab89a665154432cab36ab83b7c465388d1f27a66 Mon Sep 17 00:00:00 2001
From: "release-please[bot]"
<55107282+release-please[bot]@users.noreply.github.com>
Date: Mon, 6 Oct 2025 17:30:10 -0400
Subject: [PATCH 01/18] chore(main): release 2.58.2-SNAPSHOT (#3331)
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 4443ec3d3..bebf7a572 100644
--- a/gapic-google-cloud-storage-v2/pom.xml
+++ b/gapic-google-cloud-storage-v2/pom.xml
@@ -4,13 +4,13 @@
4.0.0com.google.api.grpcgapic-google-cloud-storage-v2
- 2.58.1
+ 2.58.2-SNAPSHOTgapic-google-cloud-storage-v2GRPC library for gapic-google-cloud-storage-v2com.google.cloudgoogle-cloud-storage-parent
- 2.58.1
+ 2.58.2-SNAPSHOT
diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml
index d1c5674a8..286175ffc 100644
--- a/google-cloud-storage-bom/pom.xml
+++ b/google-cloud-storage-bom/pom.xml
@@ -19,7 +19,7 @@
4.0.0com.google.cloudgoogle-cloud-storage-bom
- 2.58.1
+ 2.58.2-SNAPSHOTpomcom.google.cloud
@@ -69,37 +69,37 @@
com.google.cloudgoogle-cloud-storage
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.api.grpcgapic-google-cloud-storage-v2
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.api.grpcgrpc-google-cloud-storage-v2
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.api.grpcproto-google-cloud-storage-v2
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.cloudgoogle-cloud-storage-control
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.api.grpcgrpc-google-cloud-storage-control-v2
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.api.grpcproto-google-cloud-storage-control-v2
- 2.58.1
+ 2.58.2-SNAPSHOT
diff --git a/google-cloud-storage-control/pom.xml b/google-cloud-storage-control/pom.xml
index 856d03291..36fa826cf 100644
--- a/google-cloud-storage-control/pom.xml
+++ b/google-cloud-storage-control/pom.xml
@@ -5,13 +5,13 @@
4.0.0com.google.cloudgoogle-cloud-storage-control
- 2.58.1
+ 2.58.2-SNAPSHOTgoogle-cloud-storage-controlGRPC library for google-cloud-storage-controlcom.google.cloudgoogle-cloud-storage-parent
- 2.58.1
+ 2.58.2-SNAPSHOT
diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml
index 170b8b17e..c4ff68a86 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -2,7 +2,7 @@
4.0.0google-cloud-storage
- 2.58.1
+ 2.58.2-SNAPSHOTjarGoogle Cloud Storagehttps://github.com/googleapis/java-storage
@@ -12,7 +12,7 @@
com.google.cloudgoogle-cloud-storage-parent
- 2.58.1
+ 2.58.2-SNAPSHOTgoogle-cloud-storage
diff --git a/grpc-google-cloud-storage-control-v2/pom.xml b/grpc-google-cloud-storage-control-v2/pom.xml
index 42534e179..a1d12db85 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.0com.google.api.grpcgrpc-google-cloud-storage-control-v2
- 2.58.1
+ 2.58.2-SNAPSHOTgrpc-google-cloud-storage-control-v2GRPC library for google-cloud-storagecom.google.cloudgoogle-cloud-storage-parent
- 2.58.1
+ 2.58.2-SNAPSHOT
diff --git a/grpc-google-cloud-storage-v2/pom.xml b/grpc-google-cloud-storage-v2/pom.xml
index b72149d67..67fa24cff 100644
--- a/grpc-google-cloud-storage-v2/pom.xml
+++ b/grpc-google-cloud-storage-v2/pom.xml
@@ -4,13 +4,13 @@
4.0.0com.google.api.grpcgrpc-google-cloud-storage-v2
- 2.58.1
+ 2.58.2-SNAPSHOTgrpc-google-cloud-storage-v2GRPC library for grpc-google-cloud-storage-v2com.google.cloudgoogle-cloud-storage-parent
- 2.58.1
+ 2.58.2-SNAPSHOT
diff --git a/pom.xml b/pom.xml
index a8c81db55..fa0d915d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.google.cloudgoogle-cloud-storage-parentpom
- 2.58.1
+ 2.58.2-SNAPSHOTStorage Parenthttps://github.com/googleapis/java-storage
@@ -82,7 +82,7 @@
com.google.cloudgoogle-cloud-storage
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.apis
@@ -104,32 +104,32 @@
com.google.api.grpcproto-google-cloud-storage-v2
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.api.grpcgrpc-google-cloud-storage-v2
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.api.grpcgapic-google-cloud-storage-v2
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.api.grpcgrpc-google-cloud-storage-control-v2
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.api.grpcproto-google-cloud-storage-control-v2
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.cloudgoogle-cloud-storage-control
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.cloud
diff --git a/proto-google-cloud-storage-control-v2/pom.xml b/proto-google-cloud-storage-control-v2/pom.xml
index 6f868e12a..2038a1286 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.0com.google.api.grpcproto-google-cloud-storage-control-v2
- 2.58.1
+ 2.58.2-SNAPSHOTproto-google-cloud-storage-control-v2Proto library for proto-google-cloud-storage-control-v2com.google.cloudgoogle-cloud-storage-parent
- 2.58.1
+ 2.58.2-SNAPSHOT
diff --git a/proto-google-cloud-storage-v2/pom.xml b/proto-google-cloud-storage-v2/pom.xml
index 85530d5cd..b7630f746 100644
--- a/proto-google-cloud-storage-v2/pom.xml
+++ b/proto-google-cloud-storage-v2/pom.xml
@@ -4,13 +4,13 @@
4.0.0com.google.api.grpcproto-google-cloud-storage-v2
- 2.58.1
+ 2.58.2-SNAPSHOTproto-google-cloud-storage-v2PROTO library for proto-google-cloud-storage-v2com.google.cloudgoogle-cloud-storage-parent
- 2.58.1
+ 2.58.2-SNAPSHOT
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index e21f90590..87150d879 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -28,12 +28,12 @@
com.google.cloudgoogle-cloud-storage
- 2.58.1
+ 2.58.2-SNAPSHOTcom.google.cloudgoogle-cloud-storage-control
- 2.58.1
+ 2.58.2-SNAPSHOTcompile
@@ -70,7 +70,7 @@
com.google.cloudgoogle-cloud-storage
- 2.58.1
+ 2.58.2-SNAPSHOTteststest
diff --git a/storage-shared-benchmarking/pom.xml b/storage-shared-benchmarking/pom.xml
index 134504016..0f68aa834 100644
--- a/storage-shared-benchmarking/pom.xml
+++ b/storage-shared-benchmarking/pom.xml
@@ -10,7 +10,7 @@
com.google.cloudgoogle-cloud-storage-parent
- 2.58.1
+ 2.58.2-SNAPSHOT
@@ -31,7 +31,7 @@
com.google.cloudgoogle-cloud-storage
- 2.58.1
+ 2.58.2-SNAPSHOTtests
diff --git a/versions.txt b/versions.txt
index 1f8f3d6f0..353146a92 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,10 +1,10 @@
# Format:
# module:released-version:current-version
-google-cloud-storage:2.58.1:2.58.1
-gapic-google-cloud-storage-v2:2.58.1:2.58.1
-grpc-google-cloud-storage-v2:2.58.1:2.58.1
-proto-google-cloud-storage-v2:2.58.1:2.58.1
-google-cloud-storage-control:2.58.1:2.58.1
-proto-google-cloud-storage-control-v2:2.58.1:2.58.1
-grpc-google-cloud-storage-control-v2:2.58.1:2.58.1
+google-cloud-storage:2.58.1:2.58.2-SNAPSHOT
+gapic-google-cloud-storage-v2:2.58.1:2.58.2-SNAPSHOT
+grpc-google-cloud-storage-v2:2.58.1:2.58.2-SNAPSHOT
+proto-google-cloud-storage-v2:2.58.1:2.58.2-SNAPSHOT
+google-cloud-storage-control:2.58.1:2.58.2-SNAPSHOT
+proto-google-cloud-storage-control-v2:2.58.1:2.58.2-SNAPSHOT
+grpc-google-cloud-storage-control-v2:2.58.1:2.58.2-SNAPSHOT
From 64e2b2ef839e69da0605b9e53989c1f5a2b09e66 Mon Sep 17 00:00:00 2001
From: BenWhitehead
Date: Tue, 7 Oct 2025 11:39:48 -0400
Subject: [PATCH 02/18] fix: update grpc single-shot uploads to attach the
callers stracktrace as suppressed exception if an error happens in the
background (#3330)
---
.../storage/GapicUnbufferedDirectWritableByteChannel.java | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedDirectWritableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedDirectWritableByteChannel.java
index aa6bcacec..95f6472ac 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedDirectWritableByteChannel.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedDirectWritableByteChannel.java
@@ -273,11 +273,14 @@ void await() {
try {
invocationHandle.get();
} catch (InterruptedException | ExecutionException e) {
+ RuntimeException runtimeException;
if (e.getCause() instanceof RuntimeException) {
- throw (RuntimeException) e.getCause();
+ runtimeException = (RuntimeException) e.getCause();
} else {
- throw new RuntimeException(e);
+ runtimeException = new RuntimeException(e);
}
+ runtimeException.addSuppressed(new AsyncStorageTaskException());
+ throw runtimeException;
}
}
}
From 2698d43481f9ce7a9663356d536791903c21b793 Mon Sep 17 00:00:00 2001
From: BenWhitehead
Date: Tue, 7 Oct 2025 11:40:15 -0400
Subject: [PATCH 03/18] test: add preconditions to several tests so they can be
retried (#3326)
---
.../google/cloud/storage/it/ITAccessTest.java | 23 +-
.../google/cloud/storage/it/ITBucketTest.java | 100 ++-----
.../google/cloud/storage/it/ITObjectTest.java | 268 ++++++------------
3 files changed, 125 insertions(+), 266 deletions(-)
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITAccessTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITAccessTest.java
index 2b5e7b9e7..04d74ebcf 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITAccessTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITAccessTest.java
@@ -385,12 +385,18 @@ public void testEnableAndDisableUniformBucketLevelAccessOnExistingBucket() throw
BucketTargetOption.metagenerationMatch());
Bucket remoteBucket =
- storage.get(bpoBucket, Storage.BucketGetOption.fields(BucketField.IAMCONFIGURATION));
+ storage.get(
+ bpoBucket,
+ Storage.BucketGetOption.fields(
+ BucketField.IAMCONFIGURATION, BucketField.METAGENERATION));
assertTrue(remoteBucket.getIamConfiguration().isUniformBucketLevelAccessEnabled());
assertNotNull(remoteBucket.getIamConfiguration().getUniformBucketLevelAccessLockedTime());
- remoteBucket.toBuilder().setIamConfiguration(ublaDisabledIamConfiguration).build().update();
+ remoteBucket.toBuilder()
+ .setIamConfiguration(ublaDisabledIamConfiguration)
+ .build()
+ .update(BucketTargetOption.metagenerationMatch());
remoteBucket =
storage.get(
@@ -600,7 +606,10 @@ public void testRetentionPolicyNoLock() throws Exception {
assertThat(remoteBucket.retentionPolicyIsLocked()).isAnyOf(null, false);
Bucket remoteBucket2 =
- storage.get(bucketName, Storage.BucketGetOption.fields(BucketField.RETENTION_POLICY));
+ storage.get(
+ bucketName,
+ Storage.BucketGetOption.fields(
+ BucketField.RETENTION_POLICY, BucketField.METAGENERATION));
assertEquals(RETENTION_PERIOD, remoteBucket2.getRetentionPeriod());
assertThat(remoteBucket2.getRetentionPeriodDuration()).isEqualTo(RETENTION_PERIOD_DURATION);
assertNotNull(remoteBucket2.getRetentionEffectiveTime());
@@ -611,7 +620,11 @@ public void testRetentionPolicyNoLock() throws Exception {
Blob remoteBlob = storage.create(blobInfo);
assertNotNull(remoteBlob.getRetentionExpirationTime());
- Bucket remoteBucket3 = remoteBucket2.toBuilder().setRetentionPeriod(null).build().update();
+ Bucket remoteBucket3 =
+ remoteBucket2.toBuilder()
+ .setRetentionPeriod(null)
+ .build()
+ .update(BucketTargetOption.metagenerationMatch());
assertNull(remoteBucket3.getRetentionPeriod());
}
}
@@ -653,7 +666,7 @@ public void testEnableAndDisableBucketPolicyOnlyOnExistingBucket() throws Except
.setIamConfiguration(
bpoEnabledIamConfiguration.toBuilder().setIsBucketPolicyOnlyEnabled(false).build())
.build()
- .update();
+ .update(BucketTargetOption.metagenerationMatch());
remoteBucket =
storage.get(
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java
index 378f1bd45..7cf2f7f4e 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java
@@ -17,7 +17,6 @@
package com.google.cloud.storage.it;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -40,6 +39,7 @@
import com.google.cloud.storage.Rpo;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.Storage.BlobField;
+import com.google.cloud.storage.Storage.BlobTargetOption;
import com.google.cloud.storage.Storage.BucketField;
import com.google.cloud.storage.Storage.BucketGetOption;
import com.google.cloud.storage.Storage.BucketListOption;
@@ -62,12 +62,10 @@
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.StreamSupport;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -321,54 +319,6 @@ private void retentionPolicyLockRequesterPays(boolean requesterPays) {
}
}
- @Test
- // Bug in UpdateMask
- @CrossRun.Exclude(transports = Transport.GRPC)
- @Ignore("Make hermetic, currently mutates global bucket")
- public void testUpdateBucketLabel() {
- Bucket remoteBucket =
- storage.get(
- bucket.getName(), Storage.BucketGetOption.fields(BucketField.ID, BucketField.BILLING));
- assertNull(remoteBucket.getLabels());
- remoteBucket = remoteBucket.toBuilder().setLabels(BUCKET_LABELS).build();
- Bucket updatedBucket = storage.update(remoteBucket);
- assertEquals(BUCKET_LABELS, updatedBucket.getLabels());
- remoteBucket.toBuilder().setLabels(Collections.emptyMap()).build().update();
- assertNull(storage.get(bucket.getName()).getLabels());
- }
-
- @Test
- @CrossRun.Exclude(transports = Transport.GRPC)
- @Ignore("Make hermetic, currently mutates global bucket")
- public void testUpdateBucketRequesterPays() {
- // Bug in UpdateMask
- unsetRequesterPays();
- Bucket remoteBucket =
- storage.get(
- requesterPaysBucket.getName(),
- Storage.BucketGetOption.fields(BucketField.ID, BucketField.BILLING));
- assertTrue(remoteBucket.requesterPays() == null || !remoteBucket.requesterPays());
- remoteBucket = remoteBucket.toBuilder().setRequesterPays(true).build();
- Bucket updatedBucket = storage.update(remoteBucket);
- assertTrue(updatedBucket.requesterPays());
-
- String projectId = storage.getOptions().getProjectId();
- Bucket.BlobTargetOption option = Bucket.BlobTargetOption.userProject(projectId);
- String blobName = "test-create-empty-blob-requester-pays";
- Blob remoteBlob = updatedBucket.create(blobName, BLOB_BYTE_CONTENT, option);
- assertNotNull(remoteBlob);
- byte[] readBytes =
- storage.readAllBytes(
- requesterPaysBucket.getName(),
- blobName,
- Storage.BlobSourceOption.userProject(projectId));
- assertArrayEquals(BLOB_BYTE_CONTENT, readBytes);
- remoteBucket = remoteBucket.toBuilder().setRequesterPays(false).build();
- updatedBucket = storage.update(remoteBucket, Storage.BucketTargetOption.userProject(projectId));
-
- assertFalse(updatedBucket.requesterPays());
- }
-
@Test
public void testEnableDisableBucketDefaultEventBasedHold() {
String bucketName = generator.randomBucketName();
@@ -378,19 +328,30 @@ public void testEnableDisableBucketDefaultEventBasedHold() {
assertTrue(remoteBucket.getDefaultEventBasedHold());
remoteBucket =
storage.get(
- bucketName, Storage.BucketGetOption.fields(BucketField.DEFAULT_EVENT_BASED_HOLD));
+ bucketName,
+ Storage.BucketGetOption.fields(
+ BucketField.DEFAULT_EVENT_BASED_HOLD, BucketField.METAGENERATION));
assertTrue(remoteBucket.getDefaultEventBasedHold());
String blobName = generator.randomObjectName();
BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, blobName).build();
- Blob remoteBlob = storage.create(blobInfo);
+ Blob remoteBlob = storage.create(blobInfo, BlobTargetOption.doesNotExist());
assertTrue(remoteBlob.getEventBasedHold());
remoteBlob =
storage.get(
- blobInfo.getBlobId(), Storage.BlobGetOption.fields(BlobField.EVENT_BASED_HOLD));
+ blobInfo.getBlobId(),
+ Storage.BlobGetOption.fields(BlobField.EVENT_BASED_HOLD, BlobField.METAGENERATION));
assertTrue(remoteBlob.getEventBasedHold());
- remoteBlob = remoteBlob.toBuilder().setEventBasedHold(false).build().update();
+ remoteBlob =
+ remoteBlob.toBuilder()
+ .setEventBasedHold(false)
+ .build()
+ .update(BlobTargetOption.metagenerationMatch());
assertFalse(remoteBlob.getEventBasedHold());
- remoteBucket = remoteBucket.toBuilder().setDefaultEventBasedHold(false).build().update();
+ remoteBucket =
+ remoteBucket.toBuilder()
+ .setDefaultEventBasedHold(false)
+ .build()
+ .update(BucketTargetOption.metagenerationMatch());
assertFalse(remoteBucket.getDefaultEventBasedHold());
} finally {
BucketCleaner.doCleanup(bucketName, storage);
@@ -465,7 +426,9 @@ public void testObjectRetention() {
.setRetainUntilTime(now.plusHours(1))
.build())
.build()
- .update(Storage.BlobTargetOption.overrideUnlockedRetention(true));
+ .update(
+ Storage.BlobTargetOption.overrideUnlockedRetention(true),
+ BlobTargetOption.metagenerationMatch());
remoteBlob = storage.get(bucketName, "retentionObject");
assertEquals(
@@ -480,7 +443,9 @@ public void testObjectRetention() {
remoteBlob.toBuilder()
.setRetention(null)
.build()
- .update(Storage.BlobTargetOption.overrideUnlockedRetention(true));
+ .update(
+ Storage.BlobTargetOption.overrideUnlockedRetention(true),
+ BlobTargetOption.metagenerationMatch());
remoteBlob = storage.get(bucketName, "retentionObject");
assertNull(remoteBlob.getRetention());
@@ -545,7 +510,7 @@ public void testUpdateBucket_noModification() throws Exception {
storage.get(
bucket.getName(), BucketGetOption.metagenerationMatch(bucket.getMetageneration()));
- Bucket gen2 = storage.update(gen1);
+ Bucket gen2 = storage.update(gen1, BucketTargetOption.metagenerationMatch());
assertThat(gen2).isEqualTo(gen1);
}
}
@@ -606,7 +571,7 @@ public void testSoftDeletePolicy() {
.setRetentionDuration(Duration.ofDays(20))
.build())
.build()
- .update();
+ .update(BucketTargetOption.metagenerationMatch());
assertEquals(
Duration.ofDays(20),
@@ -688,19 +653,4 @@ public void testListObjectsWithFolders() throws Exception {
BucketCleaner.doCleanup(bucketName, storage);
}
}
-
- private void unsetRequesterPays() {
- Bucket remoteBucket =
- storage.get(
- requesterPaysBucket.getName(),
- Storage.BucketGetOption.fields(BucketField.ID, BucketField.BILLING),
- Storage.BucketGetOption.userProject(storage.getOptions().getProjectId()));
- // Disable requester pays in case a test fails to clean up.
- if (remoteBucket.requesterPays() != null && remoteBucket.requesterPays() == true) {
- remoteBucket.toBuilder()
- .setRequesterPays(false)
- .build()
- .update(Storage.BucketTargetOption.userProject(storage.getOptions().getProjectId()));
- }
- }
}
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectTest.java
index 3b3c3e7dc..dc2797616 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectTest.java
@@ -84,7 +84,6 @@
import java.nio.file.Paths;
import java.security.Key;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -146,7 +145,7 @@ public void testCreateBlob() {
String blobName = generator.randomObjectName();
BlobInfo blob =
BlobInfo.newBuilder(bucket, blobName).setCustomTime(System.currentTimeMillis()).build();
- Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT);
+ Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT, BlobTargetOption.doesNotExist());
assertNotNull(remoteBlob);
assertNotNull(remoteBlob.getCustomTime());
assertEquals(blob.getBucket(), remoteBlob.getBucket());
@@ -165,7 +164,7 @@ public void testCreateBlobMd5Crc32cFromHexString() {
.setMd5FromHexString("3b54781b51c94835084898e821899585")
.setCrc32cFromHexString("f4ddc43d")
.build();
- Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT);
+ Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT, BlobTargetOption.doesNotExist());
assertNotNull(remoteBlob);
assertEquals(blob.getBucket(), remoteBlob.getBucket());
assertEquals(blob.getName(), remoteBlob.getName());
@@ -180,7 +179,12 @@ public void testCreateBlobMd5Crc32cFromHexString() {
public void testCreateGetBlobWithEncryptionKey() {
String blobName = generator.randomObjectName();
BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build();
- Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT, BlobTargetOption.encryptionKey(KEY));
+ Blob remoteBlob =
+ storage.create(
+ blob,
+ BLOB_BYTE_CONTENT,
+ BlobTargetOption.encryptionKey(KEY),
+ BlobTargetOption.doesNotExist());
assertNotNull(remoteBlob);
assertEquals(blob.getBucket(), remoteBlob.getBucket());
assertEquals(blob.getName(), remoteBlob.getName());
@@ -201,7 +205,7 @@ public void testCreateGetBlobWithEncryptionKey() {
public void testCreateEmptyBlob() {
String blobName = generator.randomObjectName();
BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build();
- Blob remoteBlob = storage.create(blob);
+ Blob remoteBlob = storage.create(blob, BlobTargetOption.doesNotExist());
assertNotNull(remoteBlob);
assertEquals(blob.getBucket(), remoteBlob.getBucket());
assertEquals(blob.getName(), remoteBlob.getName());
@@ -218,7 +222,8 @@ public void testZeroByteFileUpload() throws Exception {
File zeroByteFile = File.createTempFile("zerobyte", null);
zeroByteFile.deleteOnExit();
- storage.createFrom(blobInfo, Paths.get(zeroByteFile.getAbsolutePath()));
+ storage.createFrom(
+ blobInfo, Paths.get(zeroByteFile.getAbsolutePath()), BlobWriteOption.doesNotExist());
byte[] readBytes = storage.readAllBytes(bucket.getName(), blobName);
assertArrayEquals(new byte[0], readBytes);
@@ -230,7 +235,7 @@ public void testCreateBlobStream() {
String blobName = generator.randomObjectName();
BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).setContentType(CONTENT_TYPE).build();
ByteArrayInputStream stream = new ByteArrayInputStream(BLOB_STRING_CONTENT.getBytes(UTF_8));
- Blob remoteBlob = storage.create(blob, stream);
+ Blob remoteBlob = storage.create(blob, stream, BlobWriteOption.doesNotExist());
assertNotNull(remoteBlob);
assertEquals(blob.getBucket(), remoteBlob.getBucket());
assertEquals(blob.getName(), remoteBlob.getName());
@@ -245,7 +250,9 @@ public void testCreateBlobStreamDisableGzipContent() {
String blobName = generator.randomObjectName();
BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).setContentType(CONTENT_TYPE).build();
ByteArrayInputStream stream = new ByteArrayInputStream(BLOB_STRING_CONTENT.getBytes(UTF_8));
- Blob remoteBlob = storage.create(blob, stream, BlobWriteOption.disableGzipContent());
+ Blob remoteBlob =
+ storage.create(
+ blob, stream, BlobWriteOption.disableGzipContent(), BlobWriteOption.doesNotExist());
assertNotNull(remoteBlob);
assertEquals(blob.getBucket(), remoteBlob.getBucket());
assertEquals(blob.getName(), remoteBlob.getName());
@@ -258,7 +265,7 @@ public void testCreateBlobStreamDisableGzipContent() {
public void testCreateBlobFail() {
String blobName = generator.randomObjectName();
BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build();
- Blob remoteBlob = storage.create(blob);
+ Blob remoteBlob = storage.create(blob, BlobTargetOption.doesNotExist());
assertNotNull(remoteBlob);
BlobInfo wrongGenerationBlob = BlobInfo.newBuilder(bucket, blobName, -1L).build();
try {
@@ -687,75 +694,15 @@ public void testUpdateBlob() {
BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build();
Blob remoteBlob = storage.create(blob);
assertNotNull(remoteBlob);
- Blob updatedBlob = remoteBlob.toBuilder().setContentType(CONTENT_TYPE).build().update();
- assertNotNull(updatedBlob);
- assertEquals(blob.getName(), updatedBlob.getName());
- assertEquals(blob.getBucket(), updatedBlob.getBucket());
- assertEquals(CONTENT_TYPE, updatedBlob.getContentType());
- }
-
- @Test
- public void testUpdateBlobReplaceMetadata() {
- String blobName = generator.randomObjectName();
- ImmutableMap metadata = ImmutableMap.of("k1", "a");
- ImmutableMap newMetadata = ImmutableMap.of("k2", "b");
- BlobInfo blob =
- BlobInfo.newBuilder(bucket, blobName)
+ Blob updatedBlob =
+ remoteBlob.toBuilder()
.setContentType(CONTENT_TYPE)
- .setMetadata(metadata)
- .build();
- Blob remoteBlob = storage.create(blob);
- assertNotNull(remoteBlob);
- Blob updatedBlob = remoteBlob.toBuilder().setMetadata(null).build().update();
+ .build()
+ .update(BlobTargetOption.metagenerationMatch());
assertNotNull(updatedBlob);
- assertNull(updatedBlob.getMetadata());
- updatedBlob = remoteBlob.toBuilder().setMetadata(newMetadata).build().update();
assertEquals(blob.getName(), updatedBlob.getName());
assertEquals(blob.getBucket(), updatedBlob.getBucket());
- assertEquals(newMetadata, updatedBlob.getMetadata());
- }
-
- @Test
- public void testUpdateBlobMergeMetadata() {
- String blobName = generator.randomObjectName();
- ImmutableMap metadata = ImmutableMap.of("k1", "a");
- ImmutableMap newMetadata = ImmutableMap.of("k2", "b");
- ImmutableMap expectedMetadata = ImmutableMap.of("k1", "a", "k2", "b");
- BlobInfo blob =
- BlobInfo.newBuilder(bucket, blobName)
- .setContentType(CONTENT_TYPE)
- .setMetadata(metadata)
- .build();
- Blob remoteBlob = storage.create(blob);
- assertNotNull(remoteBlob);
- Blob updatedBlob = remoteBlob.toBuilder().setMetadata(newMetadata).build().update();
- assertNotNull(updatedBlob);
- assertEquals(blob.getName(), updatedBlob.getName());
- assertEquals(blob.getBucket(), updatedBlob.getBucket());
- assertEquals(expectedMetadata, updatedBlob.getMetadata());
- }
-
- @Test
- public void testUpdateBlobUnsetMetadata() {
-
- String blobName = generator.randomObjectName();
- ImmutableMap metadata = ImmutableMap.of("k1", "a", "k2", "b");
- Map newMetadata = new HashMap<>();
- newMetadata.put("k1", "a");
- newMetadata.put("k2", null);
- ImmutableMap expectedMetadata = ImmutableMap.of("k1", "a");
- BlobInfo blob =
- BlobInfo.newBuilder(bucket, blobName)
- .setContentType(CONTENT_TYPE)
- .setMetadata(metadata)
- .build();
- Blob remoteBlob = storage.create(blob);
- assertNotNull(remoteBlob);
- Blob updatedBlob = remoteBlob.toBuilder().setMetadata(newMetadata).build().update();
- assertNotNull(updatedBlob);
- assertEquals(blob.getName(), updatedBlob.getName());
- assertEquals(blob.getBucket(), updatedBlob.getBucket());
- assertEquals(expectedMetadata, updatedBlob.getMetadata());
+ assertEquals(CONTENT_TYPE, updatedBlob.getContentType());
}
@Test
@@ -767,7 +714,9 @@ public void testUpdateBlobFail() {
BlobInfo wrongGenerationBlob =
BlobInfo.newBuilder(bucket, blobName, -1L).setContentType(CONTENT_TYPE).build();
try {
- storage.update(wrongGenerationBlob, BlobTargetOption.generationMatch());
+ storage.update(
+ wrongGenerationBlob,
+ BlobTargetOption.metagenerationMatch(remoteBlob.getMetageneration()));
fail("StorageException was expected");
} catch (StorageException ex) {
// expected
@@ -957,7 +906,8 @@ public void testCopyBlobWithPredefinedAcl() {
.setSource(source)
.setTarget(
BlobId.of(bucket.getName(), targetBlobName),
- BlobTargetOption.predefinedAcl(PredefinedAcl.PUBLIC_READ))
+ BlobTargetOption.predefinedAcl(PredefinedAcl.PUBLIC_READ),
+ BlobTargetOption.doesNotExist())
.build();
CopyWriter copyWriter = storage.copy(req);
Blob gen1 = copyWriter.getResult();
@@ -992,7 +942,10 @@ public void testCopyBlobWithEncryptionKeys() {
CopyRequest req1 =
CopyRequest.newBuilder()
.setSource(source)
- .setTarget(target, BlobTargetOption.encryptionKey(OTHER_BASE64_KEY))
+ .setTarget(
+ target,
+ BlobTargetOption.encryptionKey(OTHER_BASE64_KEY),
+ BlobTargetOption.doesNotExist())
.setSourceOptions(BlobSourceOption.decryptionKey(BASE64_KEY))
.build();
CopyWriter copyWriter1 = storage.copy(req1);
@@ -1037,7 +990,11 @@ public void testCopyBlobUpdateMetadata() {
.setContentType(CONTENT_TYPE)
.setMetadata(metadata)
.build();
- CopyRequest req = CopyRequest.of(source, target);
+ CopyRequest req =
+ CopyRequest.newBuilder()
+ .setSource(source)
+ .setTarget(target, BlobTargetOption.doesNotExist())
+ .build();
CopyWriter copyWriter = storage.copy(req);
Blob gen1 = copyWriter.getResult();
assertEquals(bucket.getName(), gen1.getBucket());
@@ -1049,52 +1006,6 @@ public void testCopyBlobUpdateMetadata() {
assertTrue(storage.delete(gen1.getBlobId()));
}
- @Test
- public void testCopyBlobUpdateStorageClass() {
- String sourceBlobName = generator.randomObjectName() + "-source";
- BlobId source = BlobId.of(bucket.getName(), sourceBlobName);
- BlobInfo sourceInfo =
- BlobInfo.newBuilder(source).setStorageClass(StorageClass.STANDARD).build();
- Blob remoteSourceBlob = storage.create(sourceInfo, BLOB_BYTE_CONTENT);
- assertNotNull(remoteSourceBlob);
- assertEquals(StorageClass.STANDARD, remoteSourceBlob.getStorageClass());
-
- String targetBlobName = generator.randomObjectName() + "-target";
- BlobInfo targetInfo =
- BlobInfo.newBuilder(bucket, targetBlobName).setStorageClass(StorageClass.COLDLINE).build();
- CopyRequest req = CopyRequest.of(source, targetInfo);
- CopyWriter copyWriter = storage.copy(req);
- Blob gen1 = copyWriter.getResult();
- assertEquals(bucket.getName(), gen1.getBucket());
- assertEquals(targetBlobName, gen1.getName());
- assertEquals(StorageClass.COLDLINE, gen1.getStorageClass());
- assertTrue(copyWriter.isDone());
- assertTrue(remoteSourceBlob.delete());
- assertTrue(storage.delete(gen1.getBlobId()));
- }
-
- @Test
- public void testCopyBlobNoContentType() {
-
- String sourceBlobName = generator.randomObjectName() + "-source";
- BlobId source = BlobId.of(bucket.getName(), sourceBlobName);
- Blob remoteSourceBlob = storage.create(BlobInfo.newBuilder(source).build(), BLOB_BYTE_CONTENT);
- assertNotNull(remoteSourceBlob);
- String targetBlobName = generator.randomObjectName() + "-target";
- ImmutableMap metadata = ImmutableMap.of("k", "v");
- BlobInfo target = BlobInfo.newBuilder(bucket, targetBlobName).setMetadata(metadata).build();
- CopyRequest req = CopyRequest.of(source, target);
- CopyWriter copyWriter = storage.copy(req);
- Blob gen1 = copyWriter.getResult();
- assertEquals(bucket.getName(), gen1.getBucket());
- assertEquals(targetBlobName, gen1.getName());
- assertTrue(gen1.getContentType() == null || gen1.getContentType().isEmpty());
- assertEquals(metadata, gen1.getMetadata());
- assertTrue(copyWriter.isDone());
- assertTrue(remoteSourceBlob.delete());
- assertTrue(storage.delete(gen1.getBlobId()));
- }
-
@Test
public void testCopyBlobFail() {
@@ -1110,7 +1021,7 @@ public void testCopyBlobFail() {
CopyRequest.newBuilder()
.setSource(bucket.getName(), sourceBlobName)
.setSourceOptions(BlobSourceOption.generationMatch(-1L))
- .setTarget(target)
+ .setTarget(target, BlobTargetOption.doesNotExist())
.build();
try {
storage.copy(req);
@@ -1137,7 +1048,9 @@ public void testReadAndWriteChannelWithEncryptionKey() throws IOException {
String blobName = generator.randomObjectName();
BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build();
byte[] stringBytes;
- try (WriteChannel writer = storage.writer(blob, BlobWriteOption.encryptionKey(BASE64_KEY))) {
+ try (WriteChannel writer =
+ storage.writer(
+ blob, BlobWriteOption.encryptionKey(BASE64_KEY), BlobWriteOption.doesNotExist())) {
stringBytes = BLOB_STRING_CONTENT.getBytes(UTF_8);
writer.write(ByteBuffer.wrap(BLOB_BYTE_CONTENT));
writer.write(ByteBuffer.wrap(stringBytes));
@@ -1197,7 +1110,7 @@ private void doTestReadAndWriteChannelsWithSize(int blobSize) throws IOException
Random rnd = new Random();
byte[] bytes = new byte[blobSize];
rnd.nextBytes(bytes);
- try (WriteChannel writer = storage.writer(blob)) {
+ try (WriteChannel writer = storage.writer(blob, BlobWriteOption.doesNotExist())) {
writer.write(ByteBuffer.wrap(bytes));
}
ByteArrayOutputStream output = new ByteArrayOutputStream();
@@ -1217,7 +1130,7 @@ public void testReadAndWriteCaptureChannels() throws IOException {
String blobName = generator.randomObjectName();
BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build();
byte[] stringBytes;
- WriteChannel writer = storage.writer(blob);
+ WriteChannel writer = storage.writer(blob, BlobWriteOption.doesNotExist());
stringBytes = BLOB_STRING_CONTENT.getBytes(UTF_8);
writer.write(ByteBuffer.wrap(BLOB_BYTE_CONTENT));
RestorableState writerState = writer.capture();
@@ -1386,12 +1299,18 @@ public void testAttemptObjectDeleteWithRetentionPolicy()
public void testEnableDisableTemporaryHold() {
String blobName = generator.randomObjectName();
BlobInfo blobInfo = BlobInfo.newBuilder(bucket, blobName).setTemporaryHold(true).build();
- Blob remoteBlob = storage.create(blobInfo);
+ Blob remoteBlob = storage.create(blobInfo, BlobTargetOption.doesNotExist());
assertTrue(remoteBlob.getTemporaryHold());
remoteBlob =
- storage.get(remoteBlob.getBlobId(), BlobGetOption.fields(BlobField.TEMPORARY_HOLD));
+ storage.get(
+ remoteBlob.getBlobId(),
+ BlobGetOption.fields(BlobField.TEMPORARY_HOLD, BlobField.METAGENERATION));
assertTrue(remoteBlob.getTemporaryHold());
- remoteBlob = remoteBlob.toBuilder().setTemporaryHold(false).build().update();
+ remoteBlob =
+ remoteBlob.toBuilder()
+ .setTemporaryHold(false)
+ .build()
+ .update(BlobTargetOption.metagenerationMatch());
assertFalse(remoteBlob.getTemporaryHold());
}
@@ -1399,7 +1318,7 @@ public void testEnableDisableTemporaryHold() {
public void testAttemptObjectDeleteWithEventBasedHold() {
String blobName = generator.randomObjectName();
BlobInfo blobInfo = BlobInfo.newBuilder(bucket, blobName).setEventBasedHold(true).build();
- Blob remoteBlob = storage.create(blobInfo);
+ Blob remoteBlob = storage.create(blobInfo, BlobTargetOption.doesNotExist());
assertTrue(remoteBlob.getEventBasedHold());
try {
remoteBlob.delete();
@@ -1415,7 +1334,7 @@ public void testAttemptObjectDeleteWithEventBasedHold() {
public void testAttemptDeletionObjectTemporaryHold() {
String blobName = generator.randomObjectName();
BlobInfo blobInfo = BlobInfo.newBuilder(bucket, blobName).setTemporaryHold(true).build();
- Blob remoteBlob = storage.create(blobInfo);
+ Blob remoteBlob = storage.create(blobInfo, BlobTargetOption.doesNotExist());
assertTrue(remoteBlob.getTemporaryHold());
try {
remoteBlob.delete();
@@ -1432,7 +1351,7 @@ public void testBlobReload() throws Exception {
String blobName = generator.randomObjectName();
BlobId blobId = BlobId.of(bucket.getName(), blobName);
BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();
- Blob blob = storage.create(blobInfo, new byte[] {0, 1, 2});
+ Blob blob = storage.create(blobInfo, new byte[] {0, 1, 2}, BlobTargetOption.doesNotExist());
Blob blobUnchanged = blob.reload();
// gRPC and json have differing defaults on projections b/258835631
@@ -1463,7 +1382,9 @@ public void testUploadWithEncryption() throws Exception {
BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();
ByteArrayInputStream content = new ByteArrayInputStream(BLOB_BYTE_CONTENT);
- Blob blob = storage.createFrom(blobInfo, content, BlobWriteOption.encryptionKey(KEY));
+ Blob blob =
+ storage.createFrom(
+ blobInfo, content, BlobWriteOption.encryptionKey(KEY), BlobWriteOption.doesNotExist());
try {
blob.getContent();
@@ -1482,18 +1403,25 @@ private Blob createBlob(String method, BlobInfo blobInfo, boolean detectType) th
switch (method) {
case "create":
return detectType
- ? storage.create(blobInfo, BlobTargetOption.detectContentType())
- : storage.create(blobInfo);
+ ? storage.create(
+ blobInfo, BlobTargetOption.detectContentType(), BlobTargetOption.doesNotExist())
+ : storage.create(blobInfo, BlobTargetOption.doesNotExist());
case "createFrom":
InputStream inputStream = new ByteArrayInputStream(BLOB_BYTE_CONTENT);
return detectType
- ? storage.createFrom(blobInfo, inputStream, BlobWriteOption.detectContentType())
- : storage.createFrom(blobInfo, inputStream);
+ ? storage.createFrom(
+ blobInfo,
+ inputStream,
+ BlobWriteOption.detectContentType(),
+ BlobWriteOption.doesNotExist())
+ : storage.createFrom(blobInfo, inputStream, BlobWriteOption.doesNotExist());
case "writer":
if (detectType) {
- storage.writer(blobInfo, BlobWriteOption.detectContentType()).close();
+ storage
+ .writer(blobInfo, BlobWriteOption.detectContentType(), BlobWriteOption.doesNotExist())
+ .close();
} else {
- storage.writer(blobInfo).close();
+ storage.writer(blobInfo, BlobWriteOption.doesNotExist()).close();
}
return storage.get(BlobId.of(blobInfo.getBucket(), blobInfo.getName()));
default:
@@ -1501,51 +1429,13 @@ private Blob createBlob(String method, BlobInfo blobInfo, boolean detectType) th
}
}
- private void testAutoContentType(String method) throws IOException {
- String[] names = {
- generator.randomObjectName() + ".txt",
- generator.randomObjectName() + "with space/Pic.Jpg",
- generator.randomObjectName() + "no_extension"
- };
- String[] types = {"text/plain", "image/jpeg", "application/octet-stream"};
- for (int i = 0; i < names.length; i++) {
- BlobId blobId = BlobId.of(bucket.getName(), names[i]);
- BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();
- Blob blob_true = createBlob(method, blobInfo, true);
- assertEquals(types[i], blob_true.getContentType());
-
- Blob blob_false = createBlob(method, blobInfo, false);
- assertThat(blob_false.getContentType()).isAnyOf("application/octet-stream", "");
- }
- String customType = "custom/type";
- BlobId blobId = BlobId.of(bucket.getName(), names[0]);
- BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType(customType).build();
- Blob blob = createBlob(method, blobInfo, true);
- assertEquals(customType, blob.getContentType());
- }
-
- @Test
- public void testAutoContentTypeCreate() throws IOException {
- testAutoContentType("create");
- }
-
- @Test
- public void testAutoContentTypeCreateFrom() throws IOException {
- testAutoContentType("createFrom");
- }
-
- @Test
- public void testAutoContentTypeWriter() throws IOException {
- testAutoContentType("writer");
- }
-
@Test
public void testBlobTimeStorageClassUpdated() {
String blobName = generator.randomObjectName();
StorageClass storageClass = StorageClass.COLDLINE;
BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).setStorageClass(storageClass).build();
- Blob remoteBlob = storage.create(blob);
+ Blob remoteBlob = storage.create(blob, BlobTargetOption.doesNotExist());
assertThat(remoteBlob).isNotNull();
assertEquals(blob.getBucket(), remoteBlob.getBucket());
assertThat(remoteBlob.getName()).isEqualTo(blob.getName());
@@ -1559,7 +1449,9 @@ public void testBlobTimeStorageClassUpdated() {
CopyRequest request =
CopyRequest.newBuilder()
.setSource(blobId)
- .setTarget(BlobInfo.newBuilder(blobId).setStorageClass(StorageClass.STANDARD).build())
+ .setTarget(
+ BlobInfo.newBuilder(blobId).setStorageClass(StorageClass.STANDARD).build(),
+ BlobTargetOption.generationMatch(remoteBlob.getGeneration()))
.build();
Blob updatedBlob1 = storage.copy(request).getResult();
assertThat(updatedBlob1.getTimeStorageClassUpdated()).isNotNull();
@@ -1570,7 +1462,11 @@ public void testBlobTimeStorageClassUpdated() {
// Updates the other properties of the blob's to check the difference between blob updateTime
// and timeStorageClassUpdated.
- Blob updatedBlob2 = updatedBlob1.toBuilder().setContentType(CONTENT_TYPE).build().update();
+ Blob updatedBlob2 =
+ updatedBlob1.toBuilder()
+ .setContentType(CONTENT_TYPE)
+ .build()
+ .update(BlobTargetOption.metagenerationMatch());
assertThat(updatedBlob2.getUpdateTime())
.isGreaterThan(updatedBlob2.getTimeStorageClassUpdated());
assertThat(updatedBlob2.getTimeStorageClassUpdated())
@@ -1583,9 +1479,9 @@ public void testUpdateBlob_noModification() {
BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build();
// in grpc, create will return acls but update does not. re-get the metadata with default fields
- Blob gen1 = storage.create(info);
+ Blob gen1 = storage.create(info, BlobTargetOption.doesNotExist());
gen1 = storage.get(gen1.getBlobId());
- Blob gen2 = storage.update(gen1);
+ Blob gen2 = storage.update(gen1, BlobTargetOption.metagenerationMatch());
assertThat(gen2).isEqualTo(gen1);
}
@@ -1601,8 +1497,8 @@ public void blob_update() throws Exception {
BlobInfo info2 =
BlobInfo.newBuilder(versionedBucket, randomObjectName).setMetadata(meta2).build();
- BlobInfo gen1 = storage.create(info1);
- BlobInfo gen2 = storage.create(info2);
+ BlobInfo gen1 = storage.create(info1, BlobTargetOption.doesNotExist());
+ BlobInfo gen2 = storage.create(info2, BlobTargetOption.generationMatch(gen1.getGeneration()));
BlobInfo update1 = gen1.toBuilder().setMetadata(meta3).build();
From 6eef1b0f587b9f32041ac4bcef1a16b1b0bc4bb3 Mon Sep 17 00:00:00 2001
From: BenWhitehead
Date: Wed, 8 Oct 2025 11:16:18 -0400
Subject: [PATCH 04/18] feat: add per-message checksum validation for gRPC
ReadObject operations (#3336)
---
.../storage/GapicDownloadSessionBuilder.java | 2 +-
.../GapicUnbufferedReadableByteChannel.java | 113 ++++++++++--------
.../cloud/storage/GrpcBlobReadChannel.java | 2 +-
...apicUnbufferedReadableByteChannelTest.java | 13 +-
4 files changed, 73 insertions(+), 57 deletions(-)
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicDownloadSessionBuilder.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicDownloadSessionBuilder.java
index 234326fa2..3bbba1d70 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicDownloadSessionBuilder.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicDownloadSessionBuilder.java
@@ -67,7 +67,7 @@ private ReadableByteChannelSessionBuilder(
this.read = read;
this.retrier = retrier;
this.resultRetryAlgorithm = resultRetryAlgorithm;
- this.hasher = Hasher.noop();
+ this.hasher = Hasher.defaultHasher();
this.autoGzipDecompression = false;
}
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedReadableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedReadableByteChannel.java
index ce41620d3..cef751213 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedReadableByteChannel.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedReadableByteChannel.java
@@ -30,6 +30,7 @@
import com.google.cloud.storage.Conversions.Decoder;
import com.google.cloud.storage.Crc32cValue.Crc32cLengthKnown;
import com.google.cloud.storage.GrpcUtils.ZeroCopyServerStreamingCallable;
+import com.google.cloud.storage.Hasher.UncheckedChecksumMismatchException;
import com.google.cloud.storage.ResponseContentLifecycleHandle.ChildRef;
import com.google.cloud.storage.Retrying.Retrier;
import com.google.cloud.storage.UnbufferedReadableByteChannelSession.UnbufferedReadableByteChannel;
@@ -104,7 +105,11 @@ public boolean shouldRetry(
boolean isWatchdogTimeout =
previousThrowable instanceof StorageException
&& previousThrowable.getCause() instanceof WatchdogTimeoutException;
- boolean shouldRetry = isWatchdogTimeout || alg.shouldRetry(previousThrowable, null);
+ boolean isChecksumMismatch =
+ previousThrowable instanceof StorageException
+ && previousThrowable.getCause() instanceof UncheckedChecksumMismatchException;
+ boolean shouldRetry =
+ isWatchdogTimeout || isChecksumMismatch || alg.shouldRetry(previousThrowable, null);
if (previousThrowable != null && !shouldRetry) {
result.setException(previousThrowable);
}
@@ -146,6 +151,16 @@ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
Thread.currentThread().interrupt();
throw new InterruptedIOException();
}
+ if (take instanceof IOException) {
+ IOException ioe = (IOException) take;
+ if (alg.shouldRetry(ioe, null)) {
+ readObjectObserver = null;
+ continue;
+ } else {
+ ioe.addSuppressed(new AsyncStorageTaskException());
+ throw ioe;
+ }
+ }
if (take instanceof Throwable) {
Throwable throwable = (Throwable) take;
BaseServiceException coalesce = StorageException.coalesce(throwable);
@@ -153,6 +168,7 @@ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
readObjectObserver = null;
continue;
} else {
+ close();
throw new IOException(coalesce);
}
}
@@ -160,45 +176,13 @@ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
complete = true;
break;
}
- readObjectObserver.request();
- ReadObjectResponse resp = (ReadObjectResponse) take;
- try (ResponseContentLifecycleHandle handle =
- read.getResponseContentLifecycleManager().get(resp)) {
- ReadObjectResponseChildRef ref = ReadObjectResponseChildRef.from(handle);
- if (resp.hasMetadata()) {
- Object respMetadata = resp.getMetadata();
- if (metadata == null) {
- metadata = respMetadata;
- } else if (metadata.getGeneration() != respMetadata.getGeneration()) {
- throw closeWithError(
- String.format(
- Locale.US,
- "Mismatch Generation between subsequent reads. Expected %d but received %d",
- metadata.getGeneration(),
- respMetadata.getGeneration()));
- }
- }
- ChecksummedData checksummedData = resp.getChecksummedData();
- ByteString content = checksummedData.getContent();
- int contentSize = content.size();
- // Very important to know whether a crc32c value is set. Without checking, protobuf will
- // happily return 0, which is a valid crc32c value.
- if (checksummedData.hasCrc32C()) {
- Crc32cLengthKnown expected = Crc32cValue.of(checksummedData.getCrc32C(), contentSize);
- try {
- hasher.validate(expected, content);
- } catch (IOException e) {
- close();
- throw e;
- }
- }
- ref.copy(c, dsts, offset, length);
- if (ref.hasRemaining()) {
- leftovers = ref;
- } else {
- ref.close();
- }
+ ReadObjectResponseChildRef ref = (ReadObjectResponseChildRef) take;
+ ref.copy(c, dsts, offset, length);
+ if (ref.hasRemaining()) {
+ leftovers = ref;
+ } else {
+ ref.close();
}
}
long read = c.read();
@@ -321,11 +305,10 @@ private void ensureStreamOpen() {
}
}
- private IOException closeWithError(String message) throws IOException {
- close();
+ private IOException createError(String message) throws IOException {
StorageException cause =
new StorageException(HttpStatusCodes.STATUS_CODE_PRECONDITION_FAILED, message);
- throw new IOException(message, cause);
+ return new IOException(message, cause);
}
private final class ReadObjectObserver extends StateCheckingResponseObserver {
@@ -335,10 +318,6 @@ private final class ReadObjectObserver extends StateCheckingResponseObserver handle =
+ read.getResponseContentLifecycleManager().get(response)) {
+ ChecksummedData checksummedData = response.getChecksummedData();
+ ByteString content = checksummedData.getContent();
+ int contentSize = content.size();
+ // Very important to know whether a crc32c value is set. Without checking, protobuf will
+ // happily return 0, which is a valid crc32c value.
+ if (checksummedData.hasCrc32C()) {
+ Crc32cLengthKnown expected = Crc32cValue.of(checksummedData.getCrc32C(), contentSize);
+ try {
+ hasher.validateUnchecked(expected, content);
+ } catch (UncheckedChecksumMismatchException e) {
+ queue.offer(e);
+ return;
+ }
+ }
+ if (response.hasMetadata()) {
+ Object respMetadata = response.getMetadata();
+ if (metadata == null) {
+ metadata = respMetadata;
+ } else if (metadata.getGeneration() != respMetadata.getGeneration()) {
+ IOException exception =
+ createError(
+ String.format(
+ Locale.US,
+ "Mismatch Generation between subsequent reads. Expected %d but received %d",
+ metadata.getGeneration(),
+ respMetadata.getGeneration()));
+ queue.offer(exception);
+ return;
+ }
+ }
+ queue.offer(ReadObjectResponseChildRef.from(handle));
+ fetchOffset.addAndGet(contentSize);
if (response.hasMetadata() && !result.isDone()) {
result.set(response.getMetadata());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw Code.ABORTED.toStatus().withCause(e).asRuntimeException();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
}
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcBlobReadChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcBlobReadChannel.java
index 5c31cb533..9113af1e0 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcBlobReadChannel.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcBlobReadChannel.java
@@ -62,7 +62,7 @@ protected LazyReadChannel, Object> newLazyReadChannel() {
ResumableMedia.gapic()
.read()
.byteChannel(read, retrier, resultRetryAlgorithm)
- .setHasher(Hasher.noop())
+ .setHasher(Hasher.defaultHasher())
.setAutoGzipDecompression(autoGzipDecompression);
BufferHandle bufferHandle = getBufferHandle();
// because we're erasing the specific type of channel, we need to declare it here.
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedReadableByteChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedReadableByteChannelTest.java
index 8006d2b53..1e1c05915 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedReadableByteChannelTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedReadableByteChannelTest.java
@@ -18,6 +18,7 @@
import static com.google.cloud.storage.TestUtils.apiException;
import static com.google.cloud.storage.TestUtils.getChecksummedData;
+import static com.google.cloud.storage.TestUtils.xxd;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
@@ -236,7 +237,7 @@ public void readObject(
}
@Test
- public void ioException_if_crc32c_mismatch_individual_message()
+ public void ifCrc32cMismatchIndividualMessage_restartFromCorrectOffset()
throws IOException, InterruptedException {
StorageGrpc.StorageImplBase fakeStorage =
new StorageGrpc.StorageImplBase() {
@@ -245,10 +246,12 @@ public void readObject(
ReadObjectRequest request, StreamObserver responseObserver) {
if (request.equals(req1)) {
responseObserver.onNext(resp1);
- ReadObjectResponse.Builder b = resp2.toBuilder();
+ responseObserver.onNext(resp2);
+ ReadObjectResponse.Builder b = resp3.toBuilder();
// set a bad checksum value
b.getChecksummedDataBuilder().setCrc32C(1);
responseObserver.onNext(b.build());
+ } else if (request.equals(req2)) {
responseObserver.onNext(resp3);
responseObserver.onNext(resp4);
responseObserver.onCompleted();
@@ -276,10 +279,10 @@ public void readObject(
retryOnly(DataLossException.class)));
byte[] actualBytes = new byte[40];
try (UnbufferedReadableByteChannel c = session.open()) {
- IOException ioException =
- assertThrows(IOException.class, () -> c.read(ByteBuffer.wrap(actualBytes)));
+ int read = c.read(ByteBuffer.wrap(actualBytes));
- assertThat(ioException).hasMessageThat().contains("Mismatch checksum");
+ assertThat(read).isEqualTo(40);
+ assertThat(xxd(actualBytes)).isEqualTo(xxd(bytes));
}
}
}
From 5cdd72f6ba7edc7cf0d6c737e4561d0621e419ae Mon Sep 17 00:00:00 2001
From: Mend Renovate
Date: Wed, 8 Oct 2025 22:29:04 +0100
Subject: [PATCH 05/18] build(deps): update dependency
org.codehaus.mojo:exec-maven-plugin to v3.6.1 (#3328)
---
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 c4ff68a86..dbc03a519 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -487,7 +487,7 @@
org.codehaus.mojoexec-maven-plugin
- 3.6.0
+ 3.6.1
From 3c395e46d7eb9aa4f1856efd85baeaec1d62750d Mon Sep 17 00:00:00 2001
From: Mend Renovate
Date: Wed, 8 Oct 2025 22:31:01 +0100
Subject: [PATCH 06/18] chore(deps): update storage release dependencies to
v2.58.1 (#3332)
---
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 d9e5315b3..a2fe523ec 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -30,12 +30,12 @@
com.google.cloudgoogle-cloud-storage
- 2.58.0
+ 2.58.1com.google.cloudgoogle-cloud-storage-control
- 2.58.0
+ 2.58.1
@@ -78,7 +78,7 @@
com.google.cloudgoogle-cloud-storage
- 2.58.0
+ 2.58.1teststest
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index 1740ae08b..c5d2382ce 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -99,7 +99,7 @@
com.google.cloudgoogle-cloud-storage
- 2.58.0
+ 2.58.1teststest
From 73f263fb2d48161de5a98612a01deba2100eddb4 Mon Sep 17 00:00:00 2001
From: Nidhi
Date: Thu, 9 Oct 2025 03:02:55 +0530
Subject: [PATCH 07/18] samples: add/modify samples for ranged reads, tail
reads of an object and appendable uploads (#3311)
---
...va => CreateAndWriteAppendableObject.java} | 17 ++-
.../object/OpenMultipleObjectsRangedRead.java | 87 +++++++++++
...java => OpenObjectMultipleRangedRead.java} | 26 +++-
...ect.java => OpenObjectReadFullObject.java} | 14 +-
...d.java => OpenObjectSingleRangedRead.java} | 19 ++-
...PauseAndResumeAppendableObjectUpload.java} | 72 ++++++----
.../object/ReadAppendableObjectTail.java | 135 ++++++++++++++++++
7 files changed, 325 insertions(+), 45 deletions(-)
rename samples/snippets/src/main/java/com/example/storage/object/{StartAppendableObjectUpload.java => CreateAndWriteAppendableObject.java} (78%)
create mode 100644 samples/snippets/src/main/java/com/example/storage/object/OpenMultipleObjectsRangedRead.java
rename samples/snippets/src/main/java/com/example/storage/object/{AppendableObjectMultipleRangedRead.java => OpenObjectMultipleRangedRead.java} (78%)
rename samples/snippets/src/main/java/com/example/storage/object/{AppendableObjectReadFullObject.java => OpenObjectReadFullObject.java} (84%)
rename samples/snippets/src/main/java/com/example/storage/object/{AppendableObjectSingleRangedRead.java => OpenObjectSingleRangedRead.java} (79%)
rename samples/snippets/src/main/java/com/example/storage/object/{ResumeAppendableObjectUpload.java => PauseAndResumeAppendableObjectUpload.java} (51%)
create mode 100644 samples/snippets/src/main/java/com/example/storage/object/ReadAppendableObjectTail.java
diff --git a/samples/snippets/src/main/java/com/example/storage/object/StartAppendableObjectUpload.java b/samples/snippets/src/main/java/com/example/storage/object/CreateAndWriteAppendableObject.java
similarity index 78%
rename from samples/snippets/src/main/java/com/example/storage/object/StartAppendableObjectUpload.java
rename to samples/snippets/src/main/java/com/example/storage/object/CreateAndWriteAppendableObject.java
index cda8b7c89..29495d0ec 100644
--- a/samples/snippets/src/main/java/com/example/storage/object/StartAppendableObjectUpload.java
+++ b/samples/snippets/src/main/java/com/example/storage/object/CreateAndWriteAppendableObject.java
@@ -16,7 +16,7 @@
package com.example.storage.object;
-// [START storage_start_appendable_object_upload]
+// [START storage_create_and_write_appendable_object_upload]
import com.google.cloud.storage.BlobAppendableUpload;
import com.google.cloud.storage.BlobAppendableUpload.AppendableUploadWriteableByteChannel;
@@ -24,6 +24,7 @@
import com.google.cloud.storage.BlobAppendableUploadConfig.CloseAction;
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.BlobInfo;
+import com.google.cloud.storage.FlushPolicy;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import com.google.common.io.ByteStreams;
@@ -33,8 +34,8 @@
import java.nio.file.Paths;
import java.util.Locale;
-public class StartAppendableObjectUpload {
- public static void startAppendableObjectUpload(
+public class CreateAndWriteAppendableObject {
+ public static void createAndWriteAppendableObject(
String bucketName, String objectName, String filePath) throws Exception {
// The ID of your GCS bucket
// String bucketName = "your-unique-bucket-name";
@@ -49,12 +50,18 @@ public static void startAppendableObjectUpload(
BlobId blobId = BlobId.of(bucketName, objectName);
BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();
+ int flushSize = 64 * 1000;
+ FlushPolicy.MaxFlushSizeFlushPolicy flushPolicy = FlushPolicy.maxFlushSize(flushSize);
BlobAppendableUploadConfig config =
- BlobAppendableUploadConfig.of().withCloseAction(CloseAction.CLOSE_WITHOUT_FINALIZING);
+ BlobAppendableUploadConfig.of()
+ .withCloseAction(CloseAction.FINALIZE_WHEN_CLOSING)
+ .withFlushPolicy(flushPolicy);
BlobAppendableUpload uploadSession = storage.blobAppendableUpload(blobInfo, config);
try (AppendableUploadWriteableByteChannel channel = uploadSession.open();
ReadableByteChannel readableByteChannel = FileChannel.open(Paths.get(filePath))) {
ByteStreams.copy(readableByteChannel, channel);
+ // Since the channel is in a try-with-resources block, channel.close()
+ // will be implicitly called here, which triggers the finalization.
} catch (IOException ex) {
throw new IOException("Failed to upload to object " + blobId.toGsUtilUri(), ex);
}
@@ -67,4 +74,4 @@ public static void startAppendableObjectUpload(
}
}
-// [END storage_start_appendable_object_upload]
+// [END storage_create_and_write_appendable_object_upload]
diff --git a/samples/snippets/src/main/java/com/example/storage/object/OpenMultipleObjectsRangedRead.java b/samples/snippets/src/main/java/com/example/storage/object/OpenMultipleObjectsRangedRead.java
new file mode 100644
index 000000000..33dfb916b
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/storage/object/OpenMultipleObjectsRangedRead.java
@@ -0,0 +1,87 @@
+/*
+ * 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.example.storage.object;
+
+// [START storage_open_multiple_objects_ranged_read]
+
+import com.google.api.core.ApiFuture;
+import com.google.api.core.ApiFutures;
+import com.google.cloud.storage.BlobId;
+import com.google.cloud.storage.BlobReadSession;
+import com.google.cloud.storage.RangeSpec;
+import com.google.cloud.storage.ReadAsFutureBytes;
+import com.google.cloud.storage.ReadProjectionConfigs;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class OpenMultipleObjectsRangedRead {
+ public static void multipleObjectsSingleRangedRead(
+ String bucketName, List objectNames, long startOffset, int length) throws Exception {
+ // The ID of your GCS bucket
+ // String bucketName = "your-unique-bucket-name";
+
+ // The ID of your GCS objects to read
+ // List objectName = Arrays.asList("object-1", "object-2", "object-3");
+
+ RangeSpec singleRange = RangeSpec.of(startOffset, length);
+ ReadAsFutureBytes rangeConfig =
+ ReadProjectionConfigs.asFutureBytes().withRangeSpec(singleRange);
+
+ try (Storage storage = StorageOptions.grpc().build().getService()) {
+ List> futuresToWaitOn = new ArrayList<>();
+
+ System.out.printf(
+ "Initiating single ranged read [%d, %d] on %d objects...%n",
+ startOffset, startOffset + length - 1, objectNames.size());
+
+ for (String objectName : objectNames) {
+ BlobId blobId = BlobId.of(bucketName, objectName);
+ ApiFuture futureReadSession = storage.blobReadSession(blobId);
+
+ ApiFuture readAndCloseFuture =
+ ApiFutures.transformAsync(
+ futureReadSession,
+ (BlobReadSession session) -> {
+ ApiFuture readFuture = session.readAs(rangeConfig);
+
+ readFuture.addListener(
+ () -> {
+ try {
+ session.close();
+ } catch (java.io.IOException e) {
+ System.err.println(
+ "WARN: Background error while closing session: " + e.getMessage());
+ }
+ },
+ MoreExecutors.directExecutor());
+ return readFuture;
+ },
+ MoreExecutors.directExecutor());
+
+ futuresToWaitOn.add(readAndCloseFuture);
+ }
+ ApiFutures.allAsList(futuresToWaitOn).get(30, TimeUnit.SECONDS);
+
+ System.out.println("All concurrent single-ranged read operations are complete.");
+ }
+ }
+}
+// [END storage_open_multiple_objects_ranged_read]
diff --git a/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectMultipleRangedRead.java b/samples/snippets/src/main/java/com/example/storage/object/OpenObjectMultipleRangedRead.java
similarity index 78%
rename from samples/snippets/src/main/java/com/example/storage/object/AppendableObjectMultipleRangedRead.java
rename to samples/snippets/src/main/java/com/example/storage/object/OpenObjectMultipleRangedRead.java
index ce36771ad..f3e823341 100644
--- a/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectMultipleRangedRead.java
+++ b/samples/snippets/src/main/java/com/example/storage/object/OpenObjectMultipleRangedRead.java
@@ -16,7 +16,7 @@
package com.example.storage.object;
-// [START storage_read_appendable_object_multiple_ranges]
+// [START storage_open_object_multiple_ranged_read]
import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutures;
@@ -30,10 +30,28 @@
import java.util.List;
import java.util.concurrent.TimeUnit;
-public class AppendableObjectMultipleRangedRead {
- public static void appendableObjectMultipleRangedRead(
+public class OpenObjectMultipleRangedRead {
+ public static void openObjectMultipleRangedRead(
String bucketName, String objectName, long offset1, int length1, long offset2, int length2)
throws Exception {
+ // The ID of your GCS bucket
+ // String bucketName = "your-unique-bucket-name";
+
+ // The ID of your GCS object
+ // String objectName = "your-object-name";
+
+ // The beginning of the range 1
+ // long offset = 0
+
+ // The maximum number of bytes to read in range 1
+ // int length = 16;
+
+ // The beginning of the range 2
+ // long offset = 16
+
+ // The maximum number of bytes to read in range 2
+ // int length = 32;
+
try (Storage storage = StorageOptions.grpc().build().getService()) {
BlobId blobId = BlobId.of(bucketName, objectName);
ApiFuture futureBlobReadSession = storage.blobReadSession(blobId);
@@ -62,4 +80,4 @@ public static void appendableObjectMultipleRangedRead(
}
}
-// [END storage_read_appendable_object_multiple_ranges]
+// [END storage_open_object_multiple_ranged_read]
diff --git a/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectReadFullObject.java b/samples/snippets/src/main/java/com/example/storage/object/OpenObjectReadFullObject.java
similarity index 84%
rename from samples/snippets/src/main/java/com/example/storage/object/AppendableObjectReadFullObject.java
rename to samples/snippets/src/main/java/com/example/storage/object/OpenObjectReadFullObject.java
index 0b24051ca..a1a22ffd2 100644
--- a/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectReadFullObject.java
+++ b/samples/snippets/src/main/java/com/example/storage/object/OpenObjectReadFullObject.java
@@ -16,7 +16,7 @@
package com.example.storage.object;
-// [START storage_read_appendable_object_full]
+// [START storage_open_object_read_full_object]
import com.google.api.core.ApiFuture;
import com.google.cloud.storage.BlobId;
@@ -30,9 +30,15 @@
import java.util.Locale;
import java.util.concurrent.TimeUnit;
-public class AppendableObjectReadFullObject {
- public static void appendableObjectReadFullObject(String bucketName, String objectName)
+public class OpenObjectReadFullObject {
+ public static void openObjectReadFullObject(String bucketName, String objectName)
throws Exception {
+ // The ID of your GCS bucket
+ // String bucketName = "your-unique-bucket-name";
+
+ // The ID of your GCS object to read
+ // String objectName = "your-object-name";
+
try (Storage storage = StorageOptions.grpc().build().getService()) {
BlobId blobId = BlobId.of(bucketName, objectName);
ApiFuture futureBlobReadSession = storage.blobReadSession(blobId);
@@ -60,4 +66,4 @@ public static void appendableObjectReadFullObject(String bucketName, String obje
}
}
}
-// [END storage_read_appendable_object_full]
+// [END storage_open_object_read_full_object]
diff --git a/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectSingleRangedRead.java b/samples/snippets/src/main/java/com/example/storage/object/OpenObjectSingleRangedRead.java
similarity index 79%
rename from samples/snippets/src/main/java/com/example/storage/object/AppendableObjectSingleRangedRead.java
rename to samples/snippets/src/main/java/com/example/storage/object/OpenObjectSingleRangedRead.java
index 437f47203..55446ea26 100644
--- a/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectSingleRangedRead.java
+++ b/samples/snippets/src/main/java/com/example/storage/object/OpenObjectSingleRangedRead.java
@@ -16,7 +16,7 @@
package com.example.storage.object;
-// [START storage_read_appendable_object_single_range]
+// [START storage_open_object_single_ranged_read]
import com.google.api.core.ApiFuture;
import com.google.cloud.storage.BlobId;
@@ -27,9 +27,20 @@
import com.google.cloud.storage.StorageOptions;
import java.util.concurrent.TimeUnit;
-public class AppendableObjectSingleRangedRead {
- public static void appendableObjectSingleRangedRead(
+public class OpenObjectSingleRangedRead {
+ public static void openObjectSingleRangedRead(
String bucketName, String objectName, long offset, int length) throws Exception {
+ // The ID of your GCS bucket
+ // String bucketName = "your-unique-bucket-name";
+
+ // The ID of your GCS object
+ // String objectName = "your-object-name";
+
+ // The beginning of the range
+ // long offset = 0
+
+ // The maximum number of bytes to read from the object.
+ // int length = 64;
try (Storage storage = StorageOptions.grpc().build().getService()) {
BlobId blobId = BlobId.of(bucketName, objectName);
@@ -55,4 +66,4 @@ public static void appendableObjectSingleRangedRead(
}
}
}
-// [END storage_read_appendable_object_single_range]
+// [END storage_open_object_single_ranged_read]
diff --git a/samples/snippets/src/main/java/com/example/storage/object/ResumeAppendableObjectUpload.java b/samples/snippets/src/main/java/com/example/storage/object/PauseAndResumeAppendableObjectUpload.java
similarity index 51%
rename from samples/snippets/src/main/java/com/example/storage/object/ResumeAppendableObjectUpload.java
rename to samples/snippets/src/main/java/com/example/storage/object/PauseAndResumeAppendableObjectUpload.java
index 8852b585d..c364ee093 100644
--- a/samples/snippets/src/main/java/com/example/storage/object/ResumeAppendableObjectUpload.java
+++ b/samples/snippets/src/main/java/com/example/storage/object/PauseAndResumeAppendableObjectUpload.java
@@ -5,7 +5,7 @@
* 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
+ * 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,
@@ -16,7 +16,7 @@
package com.example.storage.object;
-// [START storage_resume_appendable_object_upload]
+// [START storage_pause_and_resume_appendable_object_upload]
import com.google.cloud.storage.Blob;
import com.google.cloud.storage.BlobAppendableUpload;
@@ -26,20 +26,23 @@
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageChannelUtils;
import com.google.cloud.storage.StorageOptions;
import com.google.common.io.ByteStreams;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.Locale;
-public class ResumeAppendableObjectUpload {
- public static void resumeAppendableObjectUpload(
+public class PauseAndResumeAppendableObjectUpload {
+ public static void pauseAndResumeAppendableObjectUpload(
String bucketName, String objectName, String filePath) throws Exception {
// The ID of your GCS bucket
// String bucketName = "your-unique-bucket-name";
- // The ID of your GCS unfinalized appendable object
+ // The ID of your GCS object
// String objectName = "your-object-name";
// The path to the file to upload
@@ -47,45 +50,58 @@ public static void resumeAppendableObjectUpload(
try (Storage storage = StorageOptions.grpc().build().getService()) {
BlobId blobId = BlobId.of(bucketName, objectName);
- Blob existingBlob = storage.get(blobId);
- BlobInfo blobInfoForTakeover = BlobInfo.newBuilder(existingBlob.getBlobId()).build();
+ BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();
+
+ // --- Step 1: Initial string write (PAUSE) ---
+ // Default close action will be CLOSE_WITHOUT_FINALIZING
+ BlobAppendableUploadConfig initialConfig = BlobAppendableUploadConfig.of();
+ BlobAppendableUpload initialUploadSession =
+ storage.blobAppendableUpload(blobInfo, initialConfig);
+
+ try (AppendableUploadWriteableByteChannel channel = initialUploadSession.open()) {
+ String initialData = "Initial data segment.\n";
+ ByteBuffer buffer = ByteBuffer.wrap(initialData.getBytes(StandardCharsets.UTF_8));
+ long totalBytesWritten = StorageChannelUtils.blockingEmptyTo(buffer, channel);
+ channel.flush();
+
+ System.out.printf(
+ Locale.US, "Wrote %d bytes (initial string) in first segment.\n", totalBytesWritten);
+ } catch (IOException ex) {
+ throw new IOException("Failed initial upload to object " + blobId.toGsUtilUri(), ex);
+ }
+ Blob existingBlob = storage.get(blobId);
long currentObjectSize = existingBlob.getSize();
System.out.printf(
Locale.US,
- "Resuming upload for %s. Currently uploaded size: %d bytes\n",
- blobId.toGsUtilUri(),
+ "Initial upload paused. Currently uploaded size: %d bytes\n",
currentObjectSize);
- BlobAppendableUploadConfig config =
- BlobAppendableUploadConfig.of().withCloseAction(CloseAction.CLOSE_WITHOUT_FINALIZING);
+ // --- Step 2: Resume upload with file content and finalize ---
+ // Use FINALIZE_WHEN_CLOSING to ensure the object is finalized on channel closure.
+ BlobAppendableUploadConfig resumeConfig =
+ BlobAppendableUploadConfig.of().withCloseAction(CloseAction.FINALIZE_WHEN_CLOSING);
BlobAppendableUpload resumeUploadSession =
- storage.blobAppendableUpload(blobInfoForTakeover, config);
+ storage.blobAppendableUpload(existingBlob.toBuilder().build(), resumeConfig);
+
try (FileChannel fileChannel = FileChannel.open(Paths.get(filePath));
AppendableUploadWriteableByteChannel channel = resumeUploadSession.open()) {
+ long bytesToAppend = fileChannel.size();
+ System.out.printf(
+ Locale.US,
+ "Appending the entire file (%d bytes) after the initial string.\n",
+ bytesToAppend);
- if (fileChannel.size() < currentObjectSize) {
- throw new IOException(
- "Local file is smaller than the already uploaded data. File size: "
- + fileChannel.size()
- + ", Uploaded size: "
- + currentObjectSize);
- } else if (fileChannel.size() == currentObjectSize) {
- System.out.println("No more data to upload.");
- } else {
- fileChannel.position(currentObjectSize);
- System.out.printf(
- Locale.US, "Appending %d bytes\n", fileChannel.size() - currentObjectSize);
- ByteStreams.copy(fileChannel, channel);
- }
+ ByteStreams.copy(fileChannel, channel);
}
+
BlobInfo result = storage.get(blobId);
System.out.printf(
Locale.US,
- "Object %s successfully resumed. Total size: %d\n",
+ "\nObject %s successfully resumed and finalized. Total size: %d bytes\n",
result.getBlobId().toGsUtilUriWithGeneration(),
result.getSize());
}
}
}
-// [END storage_resume_appendable_object_upload]
+// [END storage_pause_and_resume_appendable_object_upload]
diff --git a/samples/snippets/src/main/java/com/example/storage/object/ReadAppendableObjectTail.java b/samples/snippets/src/main/java/com/example/storage/object/ReadAppendableObjectTail.java
new file mode 100644
index 000000000..98cf31e96
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/storage/object/ReadAppendableObjectTail.java
@@ -0,0 +1,135 @@
+/*
+ * 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.example.storage.object;
+
+// [START storage_read_appendable_object_tail]
+
+import com.google.api.core.ApiFuture;
+import com.google.cloud.storage.BlobAppendableUpload;
+import com.google.cloud.storage.BlobAppendableUpload.AppendableUploadWriteableByteChannel;
+import com.google.cloud.storage.BlobAppendableUploadConfig;
+import com.google.cloud.storage.BlobId;
+import com.google.cloud.storage.BlobInfo;
+import com.google.cloud.storage.BlobReadSession;
+import com.google.cloud.storage.FlushPolicy;
+import com.google.cloud.storage.RangeSpec;
+import com.google.cloud.storage.ReadProjectionConfigs;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageChannelUtils;
+import com.google.cloud.storage.StorageOptions;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+public class ReadAppendableObjectTail {
+ public static void readAppendableObjectTail(String bucketName, String objectName)
+ throws Exception {
+ // The ID of your GCS bucket
+ // String bucketName = "your-unique-bucket-name";
+
+ // The ID of your GCS object
+ // String objectName = "your-object-name";
+
+ try (Storage storage = StorageOptions.grpc().build().getService()) {
+ BlobId blobId = BlobId.of(bucketName, objectName);
+ BlobInfo info = BlobInfo.newBuilder(blobId).build();
+ int totalToWrite = 64 * 1000;
+ // Define our flush policy to flush small increments
+ // This is useful for demonstration purposes, but you should use more appropriate values for
+ // your workload.
+ int flushSize = totalToWrite / 8;
+ FlushPolicy.MinFlushSizeFlushPolicy flushPolicy =
+ FlushPolicy.minFlushSize(flushSize).withMaxPendingBytes(flushSize);
+ BlobAppendableUploadConfig appendableUploadConfig =
+ BlobAppendableUploadConfig.of().withFlushPolicy(flushPolicy);
+ BlobAppendableUpload upload =
+ storage.blobAppendableUpload(
+ info, appendableUploadConfig, Storage.BlobWriteOption.doesNotExist());
+ // Create the object, we'll takeover to write for our example.
+ upload.open().closeWithoutFinalizing();
+ BlobInfo gen1 = upload.getResult().get();
+ BlobAppendableUpload takeover = storage.blobAppendableUpload(gen1, appendableUploadConfig);
+
+ try (AppendableUploadWriteableByteChannel channel = takeover.open()) {
+ // Start a background thread to write some data on a periodic basis
+ // In reality, you're application would probably be doing thing in another scope
+ Thread writeThread = startWriteThread(totalToWrite, channel, flushPolicy);
+ try (BlobReadSession readSession =
+ storage.blobReadSession(gen1.getBlobId()).get(10, TimeUnit.SECONDS)) {
+ int zeroCnt = 0;
+ long read = 0;
+ while (read < totalToWrite) {
+ if (zeroCnt >= 30 && !channel.isOpen()) {
+ System.out.println("breaking");
+ break;
+ }
+ ApiFuture future =
+ readSession.readAs(
+ ReadProjectionConfigs.asFutureBytes()
+ .withRangeSpec(RangeSpec.of(read, flushPolicy.getMinFlushSize())));
+ byte[] bytes = future.get(20, TimeUnit.SECONDS);
+
+ read += bytes.length;
+ long defaultSleep = 1_500L;
+ if (bytes.length == 0) {
+ zeroCnt++;
+ long millis = defaultSleep * zeroCnt;
+ System.out.println("millis = " + millis);
+ Thread.sleep(millis);
+ } else {
+ zeroCnt = 0;
+ System.out.println("bytes.length = " + bytes.length + " read = " + read);
+ Thread.sleep(defaultSleep);
+ }
+ }
+ assert read == totalToWrite : "not enough bytes";
+ }
+ writeThread.join();
+ }
+ }
+ }
+
+ private static Thread startWriteThread(
+ int totalToWrite,
+ AppendableUploadWriteableByteChannel channel,
+ FlushPolicy.MinFlushSizeFlushPolicy flushPolicy) {
+ Thread writeThread =
+ new Thread(
+ () -> {
+ try {
+ for (long written = 0; written < totalToWrite; ) {
+ byte alphaOffset = (byte) (written % 0x1a);
+
+ ByteBuffer buf = ByteBuffer.wrap(new byte[] {(byte) (0x41 + alphaOffset)});
+ int w = StorageChannelUtils.blockingEmptyTo(buf, channel);
+ written += w;
+ if (written % flushPolicy.getMinFlushSize() == 0) {
+ channel.flush();
+ Thread.sleep(40);
+ }
+ }
+ channel.closeWithoutFinalizing();
+
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ writeThread.start();
+ return writeThread;
+ }
+}
+// [END storage_read_appendable_object_tail]
From 54bc2c12f2d0e8c164e4ddcaa1a61d2de3911131 Mon Sep 17 00:00:00 2001
From: Nidhi
Date: Thu, 9 Oct 2025 03:04:09 +0530
Subject: [PATCH 08/18] fix: add case insensitive check for
X-Goog-Content-SHA256 in SignatureInfo (#3337)
---
.../google/cloud/storage/SignatureInfo.java | 8 +++-
.../cloud/storage/SignatureInfoTest.java | 37 +++++++++++++++++++
2 files changed, 44 insertions(+), 1 deletion(-)
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java
index 03630c61a..979949635 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java
@@ -170,7 +170,13 @@ private String constructV4CanonicalRequestHash() {
.append(serializer.serializeHeaderNames(canonicalizedExtensionHeaders))
.append(COMPONENT_SEPARATOR);
- String userProvidedHash = canonicalizedExtensionHeaders.get("X-Goog-Content-SHA256");
+ String userProvidedHash = null;
+ for (Map.Entry entry : canonicalizedExtensionHeaders.entrySet()) {
+ if ("X-Goog-Content-SHA256".equalsIgnoreCase(entry.getKey())) {
+ userProvidedHash = entry.getValue();
+ break;
+ }
+ }
canonicalRequest.append(userProvidedHash == null ? "UNSIGNED-PAYLOAD" : userProvidedHash);
return Hashing.sha256()
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java
index 7b146a713..711a84d04 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java
@@ -20,7 +20,9 @@
import static org.junit.Assert.assertTrue;
import com.google.cloud.storage.SignatureInfo.Builder;
+import com.google.common.hash.Hashing;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
@@ -98,4 +100,39 @@ public void constructV4QueryString() {
+ "auto%2Fstorage%2Fgoog4_request&X-Goog-Date=20010909T014640Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host",
queryString);
}
+
+ @Test
+ public void constructV4UnsignedPayloadWithContentSha256Header() {
+ Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 10L, URI.create(RESOURCE));
+ builder.setSignatureVersion(Storage.SignUrlOption.SignatureVersion.V4);
+ builder.setAccountEmail("me@google.com");
+ builder.setTimestamp(1000000000000L);
+
+ Map extensionHeaders = new HashMap<>();
+ // Add the header with a lowercase key, which triggers the bug.
+ String contentSha256 = "sha256";
+ extensionHeaders.put("X-goog-content-sha256", contentSha256);
+ builder.setCanonicalizedExtensionHeaders(extensionHeaders);
+
+ // This is the payload hash that SHOULD be generated
+ String correctCanonicalRequest =
+ "PUT\n"
+ + "/bucketName/blobName\n"
+ + "X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=me%40google.com%2F20010909%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20010909T014640Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host%3Bx-goog-content-sha256\n"
+ + "host:storage.googleapis.com\n"
+ + "x-goog-content-sha256:"
+ + contentSha256
+ + "\n"
+ + "\n"
+ + "host;x-goog-content-sha256\n"
+ + contentSha256;
+ String expectedPayloadHash =
+ Hashing.sha256().hashString(correctCanonicalRequest, StandardCharsets.UTF_8).toString();
+
+ String unsignedPayload = builder.build().constructUnsignedPayload();
+ String[] parts = unsignedPayload.split("\n");
+ String generatedPayloadHash = parts[parts.length - 1];
+
+ assertEquals(expectedPayloadHash, generatedPayloadHash);
+ }
}
From 792ea71e49e183c8e72da70a2382bb2ff0984cf3 Mon Sep 17 00:00:00 2001
From: cloud-java-bot <122572305+cloud-java-bot@users.noreply.github.com>
Date: Thu, 9 Oct 2025 12:00:29 -0400
Subject: [PATCH 09/18] chore: Update generation configuration at Thu Oct 9
02:27:00 UTC 2025 (#3333)
---
README.md | 16 +++++++++-------
generation_config.yaml | 2 +-
2 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index dd03e0c6f..64a76e3ae 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.cloudgoogle-cloud-storage
- 2.58.0
+ 2.58.1com.google.cloudgoogle-cloud-storage-control
- 2.58.0
+ 2.58.1
```
@@ -321,9 +321,6 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-storage/tree/
| Get Managed Folder | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/managedfolders/GetManagedFolder.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/managedfolders/GetManagedFolder.java) |
| List Managed Folders | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/managedfolders/ListManagedFolders.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/managedfolders/ListManagedFolders.java) |
| Add Blob Owner | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/AddBlobOwner.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/AddBlobOwner.java) |
-| Appendable Object Multiple Ranged Read | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectMultipleRangedRead.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/AppendableObjectMultipleRangedRead.java) |
-| Appendable Object Read Full Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectReadFullObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/AppendableObjectReadFullObject.java) |
-| Appendable Object Single Ranged Read | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectSingleRangedRead.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/AppendableObjectSingleRangedRead.java) |
| Atomic Move Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/AtomicMoveObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/AtomicMoveObject.java) |
| Batch Set Object Metadata | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/BatchSetObjectMetadata.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/BatchSetObjectMetadata.java) |
| Change Object Csek To Kms | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ChangeObjectCsekToKms.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ChangeObjectCsekToKms.java) |
@@ -332,6 +329,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-storage/tree/
| Copy Delete Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/CopyDeleteObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/CopyDeleteObject.java) |
| Copy Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/CopyObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/CopyObject.java) |
| Copy Old Version Of Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/CopyOldVersionOfObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/CopyOldVersionOfObject.java) |
+| Create And Write Appendable Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/CreateAndWriteAppendableObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/CreateAndWriteAppendableObject.java) |
| Delete Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/DeleteObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/DeleteObject.java) |
| Delete Old Version Of Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/DeleteOldVersionOfObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/DeleteOldVersionOfObject.java) |
| Download Byte Range | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/DownloadByteRange.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/DownloadByteRange.java) |
@@ -351,19 +349,23 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-storage/tree/
| List Soft Deleted Objects | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ListSoftDeletedObjects.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ListSoftDeletedObjects.java) |
| List Soft Deleted Versions Of Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ListSoftDeletedVersionsOfObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ListSoftDeletedVersionsOfObject.java) |
| Make Object Public | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/MakeObjectPublic.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/MakeObjectPublic.java) |
+| Open Multiple Objects Ranged Read | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/OpenMultipleObjectsRangedRead.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/OpenMultipleObjectsRangedRead.java) |
+| Open Object Multiple Ranged Read | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/OpenObjectMultipleRangedRead.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/OpenObjectMultipleRangedRead.java) |
+| Open Object Read Full Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/OpenObjectReadFullObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/OpenObjectReadFullObject.java) |
+| Open Object Single Ranged Read | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/OpenObjectSingleRangedRead.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/OpenObjectSingleRangedRead.java) |
+| Pause And Resume Appendable Object Upload | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/PauseAndResumeAppendableObjectUpload.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/PauseAndResumeAppendableObjectUpload.java) |
| Print Blob Acl | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/PrintBlobAcl.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/PrintBlobAcl.java) |
| Print Blob Acl For User | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/PrintBlobAclForUser.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/PrintBlobAclForUser.java) |
+| Read Appendable Object Tail | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ReadAppendableObjectTail.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ReadAppendableObjectTail.java) |
| Release Event Based Hold | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ReleaseEventBasedHold.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ReleaseEventBasedHold.java) |
| Release Temporary Hold | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ReleaseTemporaryHold.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ReleaseTemporaryHold.java) |
| Remove Blob Owner | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/RemoveBlobOwner.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/RemoveBlobOwner.java) |
| Restore Soft Deleted Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/RestoreSoftDeletedObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/RestoreSoftDeletedObject.java) |
-| Resume Appendable Object Upload | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ResumeAppendableObjectUpload.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ResumeAppendableObjectUpload.java) |
| Rotate Object Encryption Key | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/RotateObjectEncryptionKey.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/RotateObjectEncryptionKey.java) |
| Set Event Based Hold | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetEventBasedHold.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetEventBasedHold.java) |
| Set Object Metadata | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetObjectMetadata.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetObjectMetadata.java) |
| Set Object Retention Policy | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetObjectRetentionPolicy.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetObjectRetentionPolicy.java) |
| Set Temporary Hold | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetTemporaryHold.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetTemporaryHold.java) |
-| Start Appendable Object Upload | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/StartAppendableObjectUpload.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/StartAppendableObjectUpload.java) |
| Stream Object Download | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/StreamObjectDownload.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/StreamObjectDownload.java) |
| Stream Object Upload | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/StreamObjectUpload.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/StreamObjectUpload.java) |
| Upload Encrypted Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/UploadEncryptedObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/UploadEncryptedObject.java) |
diff --git a/generation_config.yaml b/generation_config.yaml
index 56e27ee04..f9ad81523 100644
--- a/generation_config.yaml
+++ b/generation_config.yaml
@@ -1,5 +1,5 @@
gapic_generator_version: 2.62.3
-googleapis_commitish: 2193a2bfcecb92b92aad7a4d81baa428cafd7dfd
+googleapis_commitish: 7b2b58ff4fb3eee3c0923af35fdee90134fabe3b
libraries_bom_version: 26.69.0
libraries:
- api_shortname: storage
From 7e42c2fbca53ca6b1266f784e58cee00cfed7d62 Mon Sep 17 00:00:00 2001
From: Lawrence Qiu
Date: Thu, 9 Oct 2025 16:01:25 +0000
Subject: [PATCH 10/18] fix: migrate away from GoogleCredentials.fromStream()
usages (#3339)
---
.../storage/testing/RemoteStorageHelper.java | 60 +++++++++++++++----
1 file changed, 47 insertions(+), 13 deletions(-)
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java
index da4d96a11..606ed1226 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java
@@ -16,6 +16,7 @@
package com.google.cloud.storage.testing;
+import com.google.api.core.ObsoleteApi;
import com.google.api.gax.paging.Page;
import com.google.api.gax.retrying.RetrySettings;
import com.google.auth.oauth2.GoogleCredentials;
@@ -186,7 +187,27 @@ public static String generateBucketName() {
}
/**
- * Creates a {@code RemoteStorageHelper} object for the given project id and JSON key input
+ * This method is obsolete because of a potential security risk. Use the {@link #create(String,
+ * GoogleCredentials)} method instead.
+ *
+ *
If you know that you will be loading credential configurations of a specific type, it is
+ * recommended to use a credential-type-specific `fromStream()` method. This will ensure that an
+ * unexpected credential type with potential for malicious intent is not loaded unintentionally.
+ * You might still have to do validation for certain credential types. Please follow the
+ * recommendation for that method.
+ *
+ *
If you are loading your credential configuration from an untrusted source and have not
+ * mitigated the risks (e.g. by validating the configuration yourself), make these changes as soon
+ * as possible to prevent security risks to your environment.
+ *
+ *
Regardless of the method used, it is always your responsibility to validate configurations
+ * received from external sources.
+ *
+ *
See the {@see documentation}
+ * for more details.
+ *
+ *
Creates a {@code RemoteStorageHelper} object for the given project id and JSON key input
* stream.
*
* @param projectId id of the project to be used for running the tests
@@ -195,21 +216,12 @@ public static String generateBucketName() {
* @throws com.google.cloud.storage.testing.RemoteStorageHelper.StorageHelperException if {@code
* keyStream} is not a valid JSON key stream
*/
+ @ObsoleteApi(
+ "This method is obsolete because of a potential security risk. Use the create() variant with Credential parameter instead")
public static RemoteStorageHelper create(String projectId, InputStream keyStream)
throws StorageHelperException {
try {
- HttpTransportOptions transportOptions =
- HttpStorageOptions.defaults().getDefaultTransportOptions();
- transportOptions =
- transportOptions.toBuilder().setConnectTimeout(60000).setReadTimeout(60000).build();
- StorageOptions storageOptions =
- StorageOptions.http()
- .setCredentials(GoogleCredentials.fromStream(keyStream))
- .setProjectId(projectId)
- .setRetrySettings(retrySettings())
- .setTransportOptions(transportOptions)
- .build();
- return new RemoteStorageHelper(storageOptions);
+ return create(projectId, GoogleCredentials.fromStream(keyStream));
} catch (IOException ex) {
if (log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, ex.getMessage());
@@ -218,6 +230,28 @@ public static RemoteStorageHelper create(String projectId, InputStream keyStream
}
}
+ /**
+ * Creates a {@code RemoteStorageHelper} object for the given project id and Credential.
+ *
+ * @param projectId id of the project to be used for running the tests
+ * @param credentials GoogleCredential to set to StorageOptions
+ * @return A {@code RemoteStorageHelper} object for the provided options
+ */
+ public static RemoteStorageHelper create(String projectId, GoogleCredentials credentials) {
+ HttpTransportOptions transportOptions =
+ HttpStorageOptions.defaults().getDefaultTransportOptions();
+ transportOptions =
+ transportOptions.toBuilder().setConnectTimeout(60000).setReadTimeout(60000).build();
+ StorageOptions storageOptions =
+ StorageOptions.http()
+ .setCredentials(credentials)
+ .setProjectId(projectId)
+ .setRetrySettings(retrySettings())
+ .setTransportOptions(transportOptions)
+ .build();
+ return new RemoteStorageHelper(storageOptions);
+ }
+
/**
* Creates a {@code RemoteStorageHelper} object using default project id and authentication
* credentials.
From 9dcce11a7f5197efc984c9cbdfe1fbcb25d6a159 Mon Sep 17 00:00:00 2001
From: Nidhi
Date: Fri, 10 Oct 2025 03:58:31 +0530
Subject: [PATCH 11/18] samples: add samples for object contexts (#3329)
---
.../storage/object/GetObjectContexts.java | 65 +++++++++++++
.../storage/object/ListObjectContexts.java | 64 +++++++++++++
.../storage/object/SetObjectContexts.java | 91 +++++++++++++++++++
3 files changed, 220 insertions(+)
create mode 100644 samples/snippets/src/main/java/com/example/storage/object/GetObjectContexts.java
create mode 100644 samples/snippets/src/main/java/com/example/storage/object/ListObjectContexts.java
create mode 100644 samples/snippets/src/main/java/com/example/storage/object/SetObjectContexts.java
diff --git a/samples/snippets/src/main/java/com/example/storage/object/GetObjectContexts.java b/samples/snippets/src/main/java/com/example/storage/object/GetObjectContexts.java
new file mode 100644
index 000000000..a0ab37776
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/storage/object/GetObjectContexts.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.example.storage.object;
+
+// [START storage_get_object_contexts]
+
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.BlobInfo.ObjectContexts;
+import com.google.cloud.storage.BlobInfo.ObjectCustomContextPayload;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import java.util.Map;
+
+public class GetObjectContexts {
+ public static void getObjectContexts(String projectId, String bucketName, String objectName)
+ throws Exception {
+ // The ID of your GCP project
+ // String projectId = "your-project-id";
+
+ // The ID of your GCS bucket
+ // String bucketName = "your-unique-bucket-name";
+
+ // The ID of your GCS object
+ // String objectName = "your-object-name";
+
+ try (Storage storage =
+ StorageOptions.newBuilder().setProjectId(projectId).build().getService()) {
+
+ Blob blob = storage.get(bucketName, objectName);
+ if (blob == null) {
+ System.out.println("The object " + objectName + " was not found in " + bucketName);
+ return;
+ }
+ ObjectContexts objectContexts = blob.getContexts();
+
+ if (objectContexts != null) {
+ Map customContexts = objectContexts.getCustom();
+ if (customContexts == null) {
+ System.out.println("No custom contexts found for object: " + objectName);
+ return;
+ }
+ // Print blob's object contexts
+ System.out.println("\nCustom Contexts:");
+ for (Map.Entry custom : customContexts.entrySet()) {
+ System.out.println(custom.getKey() + "=" + custom.getValue());
+ }
+ }
+ }
+ }
+}
+// [END storage_get_object_contexts]
diff --git a/samples/snippets/src/main/java/com/example/storage/object/ListObjectContexts.java b/samples/snippets/src/main/java/com/example/storage/object/ListObjectContexts.java
new file mode 100644
index 000000000..3becd448a
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/storage/object/ListObjectContexts.java
@@ -0,0 +1,64 @@
+/*
+ * 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.example.storage.object;
+
+// [START storage_list_object_contexts]
+
+import com.google.api.gax.paging.Page;
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+
+public class ListObjectContexts {
+ public static void listObjectContexts(String projectId, String bucketName, String key)
+ throws Exception {
+ // The ID of your GCP project
+ // String projectId = "your-project-id";
+
+ // The ID of your GCS bucket
+ // String bucketName = "your-unique-bucket-name";
+
+ // The context key you want to filter
+ // String key = "your-context-key";
+
+ try (Storage storage =
+ StorageOptions.newBuilder().setProjectId(projectId).build().getService()) {
+ /*
+ * List any object that has a context with the specified key attached
+ * String filter = "contexts.\"KEY\":*";
+ *
+ * List any object that that does not have a context with the specified key attached
+ * String filter = "NOT contexts.\"KEY\":*";
+ *
+ * List any object that has a context with the specified key and value attached
+ * String filter = "contexts.\"KEY\"=\"VALUE\"";
+ *
+ * List any object that does not have a context with the specified key and value attached
+ * String filter = "NOT contexts.\"KEY\"=\"VALUE\"";
+ */
+
+ String filter = "contexts.\"" + key + "\":*";
+
+ System.out.println("Listing objects for bucket: " + bucketName + "with context key: " + key);
+ Page blobs = storage.list(bucketName, Storage.BlobListOption.filter(filter));
+ for (Blob blob : blobs.iterateAll()) {
+ System.out.println(blob.getBlobId().toGsUtilUri());
+ }
+ }
+ }
+}
+// [END storage_list_object_contexts]
diff --git a/samples/snippets/src/main/java/com/example/storage/object/SetObjectContexts.java b/samples/snippets/src/main/java/com/example/storage/object/SetObjectContexts.java
new file mode 100644
index 000000000..169399a71
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/storage/object/SetObjectContexts.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.example.storage.object;
+
+// [START storage_set_object_contexts]
+
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.BlobId;
+import com.google.cloud.storage.BlobInfo;
+import com.google.cloud.storage.BlobInfo.ObjectContexts;
+import com.google.cloud.storage.BlobInfo.ObjectCustomContextPayload;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.google.common.collect.Maps;
+import java.util.Map;
+
+public class SetObjectContexts {
+ public static void setObjectContexts(
+ String projectId, String bucketName, String objectName, String key, String value)
+ throws Exception {
+ // The ID of your GCP project
+ // String projectId = "your-project-id";
+
+ // The ID of your GCS bucket
+ // String bucketName = "your-unique-bucket-name";
+
+ // The ID of your GCS object
+ // String objectName = "your-object-name";
+
+ // The context key-value you want to add
+ // String key = "your-context-key";
+ // String value = "your-context-value";
+
+ try (Storage storage =
+ StorageOptions.newBuilder().setProjectId(projectId).build().getService()) {
+ BlobId blobId = BlobId.of(bucketName, objectName);
+ Blob blob = storage.get(blobId);
+ if (blob == null) {
+ System.out.println("The object " + objectName + " was not found in " + bucketName);
+ return;
+ }
+
+ // Recommended: Set a generation-match precondition to avoid potential race
+ // conditions and data corruptions. The request to update returns a 412 error if
+ // the object's generation number does not match your precondition.
+ Storage.BlobTargetOption precondition = Storage.BlobTargetOption.generationMatch();
+
+ // This section demonstrates how to upsert, delete all, and delete a specific context.
+
+ // To upsert a context (if the key already exists, its value is replaced;
+ // otherwise, a new key-value pair is added):
+ ObjectCustomContextPayload payload =
+ ObjectCustomContextPayload.newBuilder().setValue(value).build();
+ Map custom = Maps.newHashMap();
+ custom.put(key, payload);
+ ObjectContexts contexts = ObjectContexts.newBuilder().setCustom(custom).build();
+
+ /*
+ * To delete all existing contexts:
+ * ObjectContexts contexts = ObjectContexts.newBuilder().setCustom(null).build();
+ */
+
+ /*
+ * To delete a specific key from the context:
+ * Map custom = Maps.newHashMap();
+ * custom.put(key, null);
+ * ObjectContexts contexts = ObjectContexts.newBuilder().setCustom(custom).build();
+ */
+ BlobInfo pendingUpdate = blob.toBuilder().setContexts(contexts).build();
+ storage.update(pendingUpdate, precondition);
+
+ System.out.println(
+ "Updated custom contexts for object " + objectName + " in bucket " + bucketName);
+ }
+ }
+}
+// [END storage_set_object_contexts]
From 9fbf2d8b1cbb8fd635b2c3c28640f5dc9b3f5450 Mon Sep 17 00:00:00 2001
From: Mend Renovate
Date: Fri, 10 Oct 2025 17:18:38 +0100
Subject: [PATCH 12/18] test(deps): update dependency
io.github.classgraph:classgraph to v4.8.184 (#3343)
---
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 dbc03a519..1b8cb3e8f 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -331,7 +331,7 @@
io.github.classgraphclassgraph
- 4.8.181
+ 4.8.184test
From e94c4e69d04c994e693ad231119daf6fe4ec1431 Mon Sep 17 00:00:00 2001
From: Mend Renovate
Date: Fri, 10 Oct 2025 17:20:53 +0100
Subject: [PATCH 13/18] chore(deps): update dependency
com.google.cloud:libraries-bom to v26.70.0 (#3341)
---
samples/snippets/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index c5d2382ce..51b4c27a1 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -31,7 +31,7 @@
com.google.cloudlibraries-bom
- 26.69.0
+ 26.70.0pomimport
From be7744fea6b8e5e1b5bf1e3132ab4db21977a0e8 Mon Sep 17 00:00:00 2001
From: Mend Renovate
Date: Mon, 20 Oct 2025 18:19:44 +0100
Subject: [PATCH 14/18] test(deps): update cross product test dependencies
(#3334)
---
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 1b8cb3e8f..d161cbb0c 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -16,7 +16,7 @@
google-cloud-storage
- 1.123.5
+ 1.124.0
@@ -239,14 +239,14 @@
com.google.api.grpcproto-google-cloud-kms-v1
- 0.169.0
+ 0.171.0testcom.google.cloudgoogle-cloud-kms
- 2.78.0
+ 2.80.0test
diff --git a/pom.xml b/pom.xml
index fa0d915d3..6836a0f77 100644
--- a/pom.xml
+++ b/pom.xml
@@ -92,7 +92,7 @@
com.google.cloudgoogle-cloud-pubsub
- 1.141.5
+ 1.142.0test
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index a2fe523ec..b77249bb4 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -66,13 +66,13 @@
com.google.cloudgoogle-cloud-pubsub
- 1.141.5
+ 1.142.0testcom.google.cloudgoogle-cloud-kms
- 2.78.0
+ 2.80.0test
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index 87150d879..b0ad09d38 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -58,13 +58,13 @@
com.google.cloudgoogle-cloud-pubsub
- 1.141.5
+ 1.142.0testcom.google.cloudgoogle-cloud-kms
- 2.78.0
+ 2.80.0test
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index 51b4c27a1..36544329c 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -76,13 +76,13 @@
com.google.cloudgoogle-cloud-pubsub
- 1.141.5
+ 1.142.0testcom.google.cloudgoogle-cloud-kms
- 2.78.0
+ 2.80.0test
From e64565ab674f586ea4850408a3f30544997f4b1b Mon Sep 17 00:00:00 2001
From: Mend Renovate
Date: Tue, 21 Oct 2025 16:53:13 +0100
Subject: [PATCH 15/18] deps: update dependency
com.google.cloud:sdk-platform-java-config to v3.53.0 (#3351)
---
.github/workflows/unmanaged_dependency_check.yaml | 2 +-
google-cloud-storage-bom/pom.xml | 2 +-
pom.xml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml
index b45989f80..756c7dfc7 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.52.3
+ uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.53.0
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 286175ffc..0e212eead 100644
--- a/google-cloud-storage-bom/pom.xml
+++ b/google-cloud-storage-bom/pom.xml
@@ -24,7 +24,7 @@
com.google.cloudsdk-platform-java-config
- 3.52.3
+ 3.53.0
diff --git a/pom.xml b/pom.xml
index 6836a0f77..63f3aa18a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,7 +14,7 @@
com.google.cloudsdk-platform-java-config
- 3.52.3
+ 3.53.0
From 9f0a93eb4c6bb8aab13915ca1cb40ba9e229a2f9 Mon Sep 17 00:00:00 2001
From: BenWhitehead
Date: Tue, 21 Oct 2025 14:44:09 -0400
Subject: [PATCH 16/18] fix: update BlobReadSession channels to not implicitly
close once EOF is observed (#3344)
---
.../BaseObjectReadSessionStreamRead.java | 18 +--
.../com/google/cloud/storage/GrpcUtils.java | 3 +
.../ObjectReadSessionStreamReadTest.java | 28 +----
.../it/ITReadableByteChannelBehaviorTest.java | 104 ++++++++++++++++++
4 files changed, 121 insertions(+), 32 deletions(-)
create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITReadableByteChannelBehaviorTest.java
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseObjectReadSessionStreamRead.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseObjectReadSessionStreamRead.java
index 3ad80a198..dc71350d7 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseObjectReadSessionStreamRead.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseObjectReadSessionStreamRead.java
@@ -343,12 +343,8 @@ public boolean canShareStreamWith(ObjectReadSessionStreamRead> other) {
@Override
public void internalClose() throws IOException {
if (!closed) {
- retryContext.reset();
closed = true;
- if (leftovers != null) {
- leftovers.ref.close();
- }
- GrpcUtils.closeAll(queue);
+ internalCleanup();
}
}
@@ -378,7 +374,7 @@ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
throw new ClosedChannelException();
}
if (complete) {
- close();
+ internalCleanup();
return -1;
}
@@ -406,7 +402,7 @@ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
} else if (poll == EofMarker.INSTANCE) {
complete = true;
if (read == 0) {
- close();
+ internalCleanup();
return -1;
}
break;
@@ -442,6 +438,14 @@ private void offer(Closeable offer) throws InterruptedIOException {
}
}
+ private void internalCleanup() throws IOException {
+ retryContext.reset();
+ if (leftovers != null) {
+ leftovers.ref.close();
+ }
+ GrpcUtils.closeAll(queue);
+ }
+
/**
* The queue items are added to is a queue of {@link Closeable}. This class smuggles a Throwable
* in a no-op Closable, such that the throwable can be in the queue.
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcUtils.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcUtils.java
index 771797201..cbf5d3172 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcUtils.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcUtils.java
@@ -70,6 +70,9 @@ static GrpcCallContext contextWithBucketName(String bucketName, GrpcCallContext
* them all as suppressed exceptions on the first occurrence.
*/
static void closeAll(Collection closeables) throws IOException {
+ if (closeables.isEmpty()) {
+ return;
+ }
IOException ioException =
closeables.stream()
.filter(Objects::nonNull)
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ObjectReadSessionStreamReadTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ObjectReadSessionStreamReadTest.java
index 3e70f9c04..9db08fd63 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ObjectReadSessionStreamReadTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ObjectReadSessionStreamReadTest.java
@@ -356,31 +356,9 @@ public void streamingRead_eofShouldBeReturnedIfNoOtherBytesRead() throws Excepti
1, RangeSpec.of(0, 137), Hasher.enabled(), RetryContext.neverRetry())) {
read.eof();
assertThat(read.read(ByteBuffer.allocate(1))).isEqualTo(-1);
-
- assertAll(
- () -> assertThrows(ClosedChannelException.class, () -> read.read((ByteBuffer) null)),
- () -> assertThat(read.isOpen()).isFalse());
- }
- }
-
- @Test
- public void streamingRead_closedOnceEofIsRead() throws Exception {
- try (StreamingRead read =
- ObjectReadSessionStreamRead.streamingRead(
- 1, RangeSpec.of(0, 137), Hasher.enabled(), RetryContext.neverRetry())) {
- ByteString bytes1 = ByteString.copyFrom(DataGenerator.base64Characters().genBytes(62));
- try (ResponseContentLifecycleHandle handle = noopContentHandle(bytes1)) {
- read.accept(handle.borrow(Function.identity()));
- }
-
- ByteBuffer buf = ByteBuffer.allocate(512);
- read.read(buf);
- read.eof();
- assertThat(read.read(buf)).isEqualTo(-1);
-
- assertAll(
- () -> assertThrows(ClosedChannelException.class, () -> read.read(buf)),
- () -> assertThat(read.isOpen()).isFalse());
+ assertThat(read.isOpen()).isTrue();
+ read.close();
+ assertThat(read.isOpen()).isFalse();
}
}
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITReadableByteChannelBehaviorTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITReadableByteChannelBehaviorTest.java
new file mode 100644
index 000000000..b4082ed4f
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITReadableByteChannelBehaviorTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.it;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.cloud.ReadChannel;
+import com.google.cloud.storage.BlobId;
+import com.google.cloud.storage.BlobReadSession;
+import com.google.cloud.storage.BucketInfo;
+import com.google.cloud.storage.ReadProjectionConfig;
+import com.google.cloud.storage.ReadProjectionConfigs;
+import com.google.cloud.storage.Storage;
+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.it.runner.registry.ObjectsFixture;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(StorageITRunner.class)
+@CrossRun(
+ backends = {Backend.PROD},
+ transports = {Transport.HTTP, Transport.GRPC})
+public final class ITReadableByteChannelBehaviorTest {
+
+ @Inject public Storage storage;
+ @Inject public BucketInfo bucket;
+ @Inject public Generator generator;
+ @Inject public ObjectsFixture objectsFixture;
+
+ @Test
+ public void eofReturnedMultipleTimes_reader() throws IOException {
+ BlobId id = objectsFixture.getObj512KiB().getInfo().getBlobId();
+
+ try (ReadChannel reader = storage.reader(id)) {
+ eofReturnedMultipleTimes_doTest(reader);
+ }
+ }
+
+ @Test
+ @CrossRun.Exclude(transports = Transport.HTTP)
+ public void eofReturnedMultipleTimes_blobReadSession_channel()
+ throws ExecutionException, InterruptedException, TimeoutException, IOException {
+ eofReturnedMultipleTimes_doTestBlobReadSession(ReadProjectionConfigs.asChannel());
+ }
+
+ @Test
+ @CrossRun.Exclude(transports = Transport.HTTP)
+ public void eofReturnedMultipleTimes_blobReadSession_seekableChannel()
+ throws ExecutionException, InterruptedException, TimeoutException, IOException {
+ eofReturnedMultipleTimes_doTestBlobReadSession(ReadProjectionConfigs.asSeekableChannel());
+ }
+
+ private void eofReturnedMultipleTimes_doTestBlobReadSession(
+ ReadProjectionConfig extends ReadableByteChannel> config)
+ throws IOException, ExecutionException, InterruptedException, TimeoutException {
+ BlobId id = objectsFixture.getObj512KiB().getInfo().getBlobId();
+
+ try (BlobReadSession session = storage.blobReadSession(id).get(3, TimeUnit.SECONDS)) {
+ try (ReadableByteChannel c = session.readAs(config)) {
+ eofReturnedMultipleTimes_doTest(c);
+ }
+ }
+ }
+
+ private void eofReturnedMultipleTimes_doTest(ReadableByteChannel c) throws IOException {
+ long copy = ByteStreams.copy(c, Channels.newChannel(ByteStreams.nullOutputStream()));
+ assertThat(copy).isEqualTo(objectsFixture.getObj512KiB().getInfo().getSize());
+
+ ByteBuffer buf = ByteBuffer.allocate(8);
+ int i = ThreadLocalRandom.current().nextInt(3, 10);
+ for (int j = 0; j < i; j++) {
+ assertWithMessage("expected EOF " + j).that(c.read(buf)).isEqualTo(-1);
+ }
+ }
+}
From 6eb33311d8dd7344e30ddcb92334fd52c7c63b4d Mon Sep 17 00:00:00 2001
From: BenWhitehead
Date: Tue, 21 Oct 2025 14:44:29 -0400
Subject: [PATCH 17/18] fix: update retry logic for grpc start resumable upload
to properly handle client side deadline_exceeded (#3354)
---
.../storage/GapicUploadSessionBuilder.java | 54 ++++++++++++-------
.../google/cloud/storage/GrpcStorageImpl.java | 18 +++----
.../cloud/storage/GrpcStorageOptions.java | 20 +++++--
.../com/google/cloud/storage/FakeServer.java | 2 +-
.../GapicUploadSessionBuilderSyntaxTest.java | 22 +++++---
...apicUnbufferedWritableByteChannelTest.java | 45 ++++++++++++++++
.../ITUnbufferedResumableUploadTest.java | 4 +-
7 files changed, 120 insertions(+), 45 deletions(-)
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java
index 3ae9b8bc2..1e4001fca 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java
@@ -17,13 +17,14 @@
package com.google.cloud.storage;
import com.google.api.core.ApiFuture;
-import com.google.api.core.ApiFutures;
+import com.google.api.core.SettableApiFuture;
import com.google.api.gax.rpc.BidiStreamingCallable;
import com.google.api.gax.rpc.ClientStreamingCallable;
import com.google.api.gax.rpc.UnaryCallable;
+import com.google.cloud.storage.Conversions.Decoder;
+import com.google.cloud.storage.Retrying.RetrierWithAlg;
import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt;
import com.google.cloud.storage.UnifiedOpts.Opts;
-import com.google.common.util.concurrent.MoreExecutors;
import com.google.storage.v2.BidiWriteObjectRequest;
import com.google.storage.v2.BidiWriteObjectResponse;
import com.google.storage.v2.StartResumableWriteRequest;
@@ -53,7 +54,8 @@ GapicBidiWritableByteChannelSessionBuilder bidiByteChannel(
ApiFuture resumableWrite(
UnaryCallable callable,
WriteObjectRequest writeObjectRequest,
- Opts opts) {
+ Opts opts,
+ RetrierWithAlg retrier) {
StartResumableWriteRequest.Builder b = StartResumableWriteRequest.newBuilder();
if (writeObjectRequest.hasWriteObjectSpec()) {
b.setWriteObjectSpec(writeObjectRequest.getWriteObjectSpec());
@@ -68,23 +70,27 @@ ApiFuture resumableWrite(
Function f =
uploadId ->
writeObjectRequest.toBuilder().clearWriteObjectSpec().setUploadId(uploadId).build();
- ApiFuture futureResumableWrite =
- ApiFutures.transform(
- callable.futureCall(req),
- (resp) -> new ResumableWrite(req, resp, f),
- MoreExecutors.directExecutor());
- // make sure we wrap any failure as a storage exception
- return ApiFutures.catchingAsync(
- futureResumableWrite,
- Throwable.class,
- throwable -> ApiFutures.immediateFailedFuture(StorageException.coalesce(throwable)),
- MoreExecutors.directExecutor());
+ SettableApiFuture future = SettableApiFuture.create();
+ try {
+ ResumableWrite resumableWrite =
+ retrier.run(
+ () -> {
+ StartResumableWriteResponse resp = callable.call(req);
+ return new ResumableWrite(req, resp, f);
+ },
+ Decoder.identity());
+ future.set(resumableWrite);
+ } catch (StorageException e) {
+ future.setException(e);
+ }
+ return future;
}
ApiFuture bidiResumableWrite(
UnaryCallable x,
BidiWriteObjectRequest writeObjectRequest,
- Opts opts) {
+ Opts opts,
+ RetrierWithAlg retrier) {
StartResumableWriteRequest.Builder b = StartResumableWriteRequest.newBuilder();
if (writeObjectRequest.hasWriteObjectSpec()) {
b.setWriteObjectSpec(writeObjectRequest.getWriteObjectSpec());
@@ -99,9 +105,19 @@ ApiFuture bidiResumableWrite(
Function f =
uploadId ->
writeObjectRequest.toBuilder().clearWriteObjectSpec().setUploadId(uploadId).build();
- return ApiFutures.transform(
- x.futureCall(req),
- (resp) -> new BidiResumableWrite(req, resp, f),
- MoreExecutors.directExecutor());
+ SettableApiFuture future = SettableApiFuture.create();
+ try {
+ BidiResumableWrite resumableWrite =
+ retrier.run(
+ () -> {
+ StartResumableWriteResponse resp = x.call(req);
+ return new BidiResumableWrite(req, resp, f);
+ },
+ Decoder.identity());
+ future.set(resumableWrite);
+ } catch (StorageException e) {
+ future.setException(e);
+ }
+ return future;
}
}
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java
index ce3e6c62d..d79b30e1f 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java
@@ -21,7 +21,6 @@
import static com.google.cloud.storage.ByteSizeConstants._256KiB;
import static com.google.cloud.storage.CrossTransportUtils.fmtMethodName;
import static com.google.cloud.storage.CrossTransportUtils.throwHttpJsonOnly;
-import static com.google.cloud.storage.GrpcToHttpStatusCodeTranslation.resultRetryAlgorithmToCodes;
import static com.google.cloud.storage.StorageV2ProtoUtils.bucketAclEntityOrAltEq;
import static com.google.cloud.storage.StorageV2ProtoUtils.objectAclEntityOrAltEq;
import static com.google.cloud.storage.Utils.bucketNameCodec;
@@ -42,7 +41,6 @@
import com.google.api.gax.rpc.ApiExceptions;
import com.google.api.gax.rpc.ClientStreamingCallable;
import com.google.api.gax.rpc.NotFoundException;
-import com.google.api.gax.rpc.StatusCode;
import com.google.api.gax.rpc.UnaryCallable;
import com.google.cloud.BaseService;
import com.google.cloud.Policy;
@@ -1831,30 +1829,26 @@ private UnbufferedReadableByteChannelSession