Skip to content

Commit 0394354

Browse files
feat: adds support for restore token (#2768)
* feat: adds support for restore token * add grpc * IT fixes
1 parent aef367d commit 0394354

File tree

10 files changed

+130
-6
lines changed

10 files changed

+130
-6
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java

+6
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,12 @@ Builder setHardDeleteTime(OffsetDateTime hardDeleteTime) {
534534
return this;
535535
}
536536

537+
@Override
538+
Builder setRestoreToken(String restoreToken) {
539+
infoBuilder.setRestoreToken(restoreToken);
540+
return this;
541+
}
542+
537543
@Override
538544
public Builder setRetention(Retention retention) {
539545
infoBuilder.setRetention(retention);

google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java

+27-2
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public class BlobInfo implements Serializable {
110110
private final Retention retention;
111111
private final OffsetDateTime softDeleteTime;
112112
private final OffsetDateTime hardDeleteTime;
113+
private final String restoreToken;
113114
private final transient ImmutableSet<NamedField> modifiedFields;
114115

115116
/** This class is meant for internal use only. Users are discouraged from using this class. */
@@ -531,6 +532,8 @@ Builder setRetentionExpirationTimeOffsetDateTime(OffsetDateTime retentionExpirat
531532

532533
abstract Builder setHardDeleteTime(OffsetDateTime hardDeleteTIme);
533534

535+
abstract Builder setRestoreToken(String restoreToken);
536+
534537
public abstract Builder setRetention(Retention retention);
535538

536539
/** Creates a {@code BlobInfo} object. */
@@ -634,6 +637,7 @@ static final class BuilderImpl extends Builder {
634637
private Retention retention;
635638
private OffsetDateTime softDeleteTime;
636639
private OffsetDateTime hardDeleteTime;
640+
private String restoreToken;
637641
private final ImmutableSet.Builder<NamedField> modifiedFields = ImmutableSet.builder();
638642

639643
BuilderImpl(BlobId blobId) {
@@ -674,6 +678,7 @@ static final class BuilderImpl extends Builder {
674678
retention = blobInfo.retention;
675679
softDeleteTime = blobInfo.softDeleteTime;
676680
hardDeleteTime = blobInfo.hardDeleteTime;
681+
restoreToken = blobInfo.restoreToken;
677682
}
678683

679684
@Override
@@ -1065,6 +1070,15 @@ Builder setHardDeleteTime(OffsetDateTime hardDeleteTime) {
10651070
return this;
10661071
}
10671072

1073+
@Override
1074+
Builder setRestoreToken(String restoreToken) {
1075+
if (!Objects.equals(this.restoreToken, restoreToken)) {
1076+
modifiedFields.add(BlobField.RESTORE_TOKEN);
1077+
}
1078+
this.restoreToken = restoreToken;
1079+
return this;
1080+
}
1081+
10681082
@Override
10691083
public Builder setRetention(Retention retention) {
10701084
// todo: b/308194853
@@ -1299,6 +1313,7 @@ Builder clearRetentionExpirationTime() {
12991313
retention = builder.retention;
13001314
softDeleteTime = builder.softDeleteTime;
13011315
hardDeleteTime = builder.hardDeleteTime;
1316+
restoreToken = builder.restoreToken;
13021317
modifiedFields = builder.modifiedFields.build();
13031318
}
13041319

@@ -1704,6 +1719,14 @@ public OffsetDateTime getHardDeleteTime() {
17041719
return hardDeleteTime;
17051720
}
17061721

1722+
/**
1723+
* If this is a soft-deleted object in an HNS-enabled bucket, returns the restore token which will
1724+
* be necessary to restore it if there's a name conflict with another object.
1725+
*/
1726+
public String getRestoreToken() {
1727+
return restoreToken;
1728+
}
1729+
17071730
/** Returns the object's Retention policy. */
17081731
public Retention getRetention() {
17091732
return retention;
@@ -1761,7 +1784,8 @@ public int hashCode() {
17611784
retention,
17621785
retentionExpirationTime,
17631786
softDeleteTime,
1764-
hardDeleteTime);
1787+
hardDeleteTime,
1788+
restoreToken);
17651789
}
17661790

17671791
@Override
@@ -1805,7 +1829,8 @@ public boolean equals(Object o) {
18051829
&& Objects.equals(retentionExpirationTime, blobInfo.retentionExpirationTime)
18061830
&& Objects.equals(retention, blobInfo.retention)
18071831
&& Objects.equals(softDeleteTime, blobInfo.softDeleteTime)
1808-
&& Objects.equals(hardDeleteTime, blobInfo.hardDeleteTime);
1832+
&& Objects.equals(hardDeleteTime, blobInfo.hardDeleteTime)
1833+
&& Objects.equals(restoreToken, blobInfo.restoreToken);
18091834
}
18101835

18111836
ImmutableSet<NamedField> getModifiedFields() {

google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcConversions.java

+4
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,7 @@ private Object blobInfoEncode(BlobInfo from) {
834834
ifNonNull(from.getCustomTimeOffsetDateTime(), timestampCodec::encode, toBuilder::setCustomTime);
835835
ifNonNull(from.getSoftDeleteTime(), timestampCodec::encode, toBuilder::setSoftDeleteTime);
836836
ifNonNull(from.getHardDeleteTime(), timestampCodec::encode, toBuilder::setHardDeleteTime);
837+
ifNonNull(from.getRestoreToken(), toBuilder::setRestoreToken);
837838
ifNonNull(
838839
from.getCustomerEncryption(),
839840
customerEncryptionCodec::encode,
@@ -905,6 +906,9 @@ private BlobInfo blobInfoDecode(Object from) {
905906
if (from.hasHardDeleteTime()) {
906907
toBuilder.setHardDeleteTime(timestampCodec.decode(from.getHardDeleteTime()));
907908
}
909+
if (from.hasRestoreToken()) {
910+
toBuilder.setRestoreToken(from.getRestoreToken());
911+
}
908912
String storageClass = from.getStorageClass();
909913
if (!storageClass.isEmpty()) {
910914
toBuilder.setStorageClass(StorageClass.valueOf(storageClass));

google-cloud-storage/src/main/java/com/google/cloud/storage/JsonConversions.java

+2
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ private StorageObject blobInfoEncode(BlobInfo from) {
254254

255255
ifNonNull(from.getSoftDeleteTime(), dateTimeCodec::encode, to::setSoftDeleteTime);
256256
ifNonNull(from.getHardDeleteTime(), dateTimeCodec::encode, to::setHardDeleteTime);
257+
ifNonNull(from.getRestoreToken(), to::setRestoreToken);
257258

258259
// todo: clean this up once retention is enabled in grpc
259260
// This is a workaround so that explicitly null retention objects are only included when the
@@ -338,6 +339,7 @@ private BlobInfo blobInfoDecode(StorageObject from) {
338339
ifNonNull(from.getRetention(), this::retentionDecode, to::setRetention);
339340
ifNonNull(from.getSoftDeleteTime(), dateTimeCodec::decode, to::setSoftDeleteTime);
340341
ifNonNull(from.getHardDeleteTime(), dateTimeCodec::decode, to::setHardDeleteTime);
342+
ifNonNull(from.getRestoreToken(), to::setRestoreToken);
341343
return to.build();
342344
}
343345

google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java

+23-1
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,10 @@ enum BlobField implements FieldSelector, NamedField {
326326

327327
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
328328
HARD_DELETE_TIME(
329-
"hardDeleteTime", "hard_delete_time", com.google.api.client.util.DateTime.class);
329+
"hardDeleteTime", "hard_delete_time", com.google.api.client.util.DateTime.class),
330330

331+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
332+
RESTORE_TOKEN("restoreToken", "restore_token", String.class);
331333
static final List<NamedField> REQUIRED_FIELDS = ImmutableList.of(BUCKET, NAME);
332334
private static final Map<String, BlobField> JSON_FIELD_NAME_INDEX;
333335

@@ -1656,6 +1658,16 @@ public static BlobGetOption softDeleted(boolean softDeleted) {
16561658
return new BlobGetOption(UnifiedOpts.softDeleted(softDeleted));
16571659
}
16581660

1661+
/**
1662+
* Returns an option that must be specified when getting a soft-deleted object from an
1663+
* HNS-enabled bucket that has a name/generation conflict with another object in the same
1664+
* bucket.
1665+
*/
1666+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
1667+
public static BlobGetOption restoreToken(String restoreToken) {
1668+
return new BlobGetOption(UnifiedOpts.restoreToken(restoreToken));
1669+
}
1670+
16591671
/**
16601672
* Deduplicate any options which are the same parameter. The value which comes last in {@code
16611673
* os} will be the value included in the return.
@@ -1741,6 +1753,16 @@ public static BlobRestoreOption metagenerationNotMatch(long generation) {
17411753
public static BlobRestoreOption copySourceAcl(boolean copySourceAcl) {
17421754
return new BlobRestoreOption(UnifiedOpts.copySourceAcl(copySourceAcl));
17431755
}
1756+
1757+
/**
1758+
* Returns an option that must be specified when getting a soft-deleted object from an
1759+
* HNS-enabled bucket that has a name/generation conflict with another object in the same
1760+
* bucket.
1761+
*/
1762+
@TransportCompatibility({Transport.HTTP, Transport.GRPC})
1763+
public static BlobRestoreOption restoreToken(String restoreToken) {
1764+
return new BlobRestoreOption(UnifiedOpts.restoreToken(restoreToken));
1765+
}
17441766
}
17451767

17461768
/** Class for specifying bucket list options. */

google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java

+23
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,10 @@ static SoftDeleted softDeleted(boolean softDeleted) {
477477
return new SoftDeleted(softDeleted);
478478
}
479479

480+
static RestoreToken restoreToken(String restoreToken) {
481+
return new RestoreToken(restoreToken);
482+
}
483+
480484
static CopySourceAcl copySourceAcl(boolean copySourceAcl) {
481485
return new CopySourceAcl(copySourceAcl);
482486
}
@@ -694,6 +698,25 @@ public Mapper<GetObjectRequest.Builder> getObject() {
694698
}
695699
}
696700

701+
static final class RestoreToken extends RpcOptVal<String> implements ObjectSourceOpt {
702+
703+
private static final long serialVersionUID = 4215757108268532746L;
704+
705+
private RestoreToken(String val) {
706+
super(StorageRpc.Option.RESTORE_TOKEN, val);
707+
}
708+
709+
@Override
710+
public Mapper<RestoreObjectRequest.Builder> restoreObject() {
711+
return b -> b.setRestoreToken(val);
712+
}
713+
714+
@Override
715+
public Mapper<GetObjectRequest.Builder> getObject() {
716+
return b -> b.setRestoreToken(val);
717+
}
718+
}
719+
697720
static final class CopySourceAcl extends RpcOptVal<Boolean> implements ObjectSourceOpt {
698721

699722
private static final long serialVersionUID = 2033755749149128119L;

google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ enum Option {
7575
RETURN_RAW_INPUT_STREAM("returnRawInputStream"),
7676
OVERRIDE_UNLOCKED_RETENTION("overrideUnlockedRetention"),
7777
SOFT_DELETED("softDeleted"),
78+
RESTORE_TOKEN("restoreToken"),
7879
COPY_SOURCE_ACL("copySourceAcl"),
7980
GENERATION("generation"),
8081
INCLUDE_FOLDERS_AS_PREFIXES("includeFoldersAsPrefixes"),

google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobReadMaskTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ public ImmutableList<?> parameters() {
203203
BlobField.RETENTION,
204204
LazyAssertion.skip("TODO: jesse fill in buganizer bug here")),
205205
new Args<>(BlobField.SOFT_DELETE_TIME, LazyAssertion.equal()),
206-
new Args<>(BlobField.HARD_DELETE_TIME, LazyAssertion.equal()));
206+
new Args<>(BlobField.HARD_DELETE_TIME, LazyAssertion.equal()),
207+
new Args<>(BlobField.RESTORE_TOKEN, LazyAssertion.equal()));
207208
List<String> argsDefined =
208209
args.stream().map(Args::getField).map(Enum::name).sorted().collect(Collectors.toList());
209210

google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectTest.java

+38
Original file line numberDiff line numberDiff line change
@@ -1524,4 +1524,42 @@ public void testUpdateBlob_noModification() {
15241524
Blob gen2 = storage.update(gen1);
15251525
assertThat(gen2).isEqualTo(gen1);
15261526
}
1527+
1528+
@Test
1529+
public void testRestoreToken() {
1530+
String bucketName = generator.randomBucketName();
1531+
storage.create(
1532+
BucketInfo.newBuilder(bucketName)
1533+
.setHierarchicalNamespace(
1534+
BucketInfo.HierarchicalNamespace.newBuilder().setEnabled(true).build())
1535+
.setIamConfiguration(
1536+
BucketInfo.IamConfiguration.newBuilder()
1537+
.setIsUniformBucketLevelAccessEnabled(true)
1538+
.build())
1539+
.build());
1540+
BlobInfo info = BlobInfo.newBuilder(bucketName, generator.randomObjectName()).build();
1541+
try {
1542+
Blob delobj = storage.create(info);
1543+
storage.delete(delobj.getBlobId());
1544+
1545+
Blob got = storage.get(delobj.getBlobId(), BlobGetOption.softDeleted(true));
1546+
assertThat(got.getRestoreToken()).isNotNull();
1547+
1548+
Blob gotWithRestoreToken =
1549+
storage.get(
1550+
delobj.getBlobId(),
1551+
BlobGetOption.softDeleted(true),
1552+
BlobGetOption.restoreToken(got.getRestoreToken()));
1553+
assertThat(gotWithRestoreToken).isNotNull();
1554+
1555+
storage.restore(
1556+
got.getBlobId(), Storage.BlobRestoreOption.restoreToken(got.getRestoreToken()));
1557+
assertThat(storage.get(bucketName, delobj.getName())).isNotNull();
1558+
;
1559+
1560+
} finally {
1561+
storage.delete(info.getBlobId());
1562+
storage.delete(bucketName);
1563+
}
1564+
}
15271565
}

google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITOptionRegressionTest.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,8 @@ public void storage_BlobGetOption_fields_BlobField() {
743743
"updated",
744744
"retention",
745745
"softDeleteTime",
746-
"hardDeleteTime");
746+
"hardDeleteTime",
747+
"restoreToken");
747748
s.get(o.getBlobId(), BlobGetOption.fields(BlobField.values()));
748749
requestAuditing.assertQueryParam("fields", expected, splitOnCommaToSet());
749750
}
@@ -923,7 +924,8 @@ public void storage_BlobListOption_fields_BlobField() {
923924
"items/updated",
924925
"items/retention",
925926
"items/softDeleteTime",
926-
"items/hardDeleteTime");
927+
"items/hardDeleteTime",
928+
"items/restoreToken");
927929
s.list(b.getName(), BlobListOption.fields(BlobField.values()));
928930
requestAuditing.assertQueryParam("fields", expected, splitOnCommaToSet());
929931
}

0 commit comments

Comments
 (0)