From 57853ec7fbc2f3188d8da991001660a4f6008632 Mon Sep 17 00:00:00 2001 From: Ajit Thakor <49403056+athakor@users.noreply.github.com> Date: Thu, 3 Sep 2020 21:40:50 +0530 Subject: [PATCH] feat: expose timeStorageClassUpdated property of blob's (#456) * feat: expose timeStorageClassUpdated field of blob * fix: package imports * feat: update javadoc * feat: add exception to avoid breaking change --- .../java/com/google/cloud/storage/Blob.java | 6 +++ .../com/google/cloud/storage/BlobInfo.java | 34 +++++++++++++++++ .../com/google/cloud/storage/Storage.java | 3 +- .../google/cloud/storage/BlobInfoTest.java | 5 +++ .../com/google/cloud/storage/BlobTest.java | 10 +++++ .../cloud/storage/it/ITStorageTest.java | 38 +++++++++++++++++++ 6 files changed, 95 insertions(+), 1 deletion(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java index 398eb3166..6faef6c0f 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java @@ -410,6 +410,12 @@ public Builder setStorageClass(StorageClass storageClass) { return this; } + @Override + public Builder setTimeStorageClassUpdated(Long timeStorageClassUpdated) { + infoBuilder.setTimeStorageClassUpdated(timeStorageClassUpdated); + return this; + } + @Override Builder setMetageneration(Long metageneration) { infoBuilder.setMetageneration(metageneration); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java index 67256d1c7..835e0d453 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java @@ -96,6 +96,7 @@ public StorageObject apply(BlobInfo blobInfo) { private final String contentDisposition; private final String contentLanguage; private final StorageClass storageClass; + private final Long timeStorageClassUpdated; private final Integer componentCount; private final boolean isDirectory; private final CustomerEncryption customerEncryption; @@ -297,6 +298,16 @@ public Builder setCustomTime(Long customTime) { /** Sets the blob's storage class. */ public abstract Builder setStorageClass(StorageClass storageClass); + /** + * Sets the modification time of an object's storage class. Once set it can't be unset directly, + * the only way is to rewrite the object with the desired storage class. + */ + public Builder setTimeStorageClassUpdated(Long timeStorageClassUpdated) { + throw new UnsupportedOperationException( + "Override setTimeStorageClassUpdated with your own implementation," + + " or use com.google.cloud.storage.Blob."); + } + /** Sets the blob's user provided metadata. */ public abstract Builder setMetadata(Map metadata); @@ -356,6 +367,7 @@ static final class BuilderImpl extends Builder { private Boolean isDirectory; private CustomerEncryption customerEncryption; private StorageClass storageClass; + private Long timeStorageClassUpdated; private String kmsKeyName; private Boolean eventBasedHold; private Boolean temporaryHold; @@ -391,6 +403,7 @@ static final class BuilderImpl extends Builder { createTime = blobInfo.createTime; isDirectory = blobInfo.isDirectory; storageClass = blobInfo.storageClass; + timeStorageClassUpdated = blobInfo.timeStorageClassUpdated; kmsKeyName = blobInfo.kmsKeyName; eventBasedHold = blobInfo.eventBasedHold; temporaryHold = blobInfo.temporaryHold; @@ -564,6 +577,12 @@ public Builder setStorageClass(StorageClass storageClass) { return this; } + @Override + public Builder setTimeStorageClassUpdated(Long timeStorageClassUpdated) { + this.timeStorageClassUpdated = timeStorageClassUpdated; + return this; + } + @Override Builder setMetageneration(Long metageneration) { this.metageneration = metageneration; @@ -657,6 +676,7 @@ public BlobInfo build() { createTime = builder.createTime; isDirectory = firstNonNull(builder.isDirectory, Boolean.FALSE); storageClass = builder.storageClass; + timeStorageClassUpdated = builder.timeStorageClassUpdated; kmsKeyName = builder.kmsKeyName; eventBasedHold = builder.eventBasedHold; temporaryHold = builder.temporaryHold; @@ -917,6 +937,14 @@ public StorageClass getStorageClass() { return storageClass; } + /** + * Returns the time that the object's storage class was last changed or the time of the object + * creation. + */ + public Long getTimeStorageClassUpdated() { + return timeStorageClassUpdated; + } + /** Returns the Cloud KMS key used to encrypt the blob, if any. */ public String getKmsKeyName() { return kmsKeyName; @@ -1049,6 +1077,9 @@ public ObjectAccessControl apply(Acl acl) { if (storageClass != null) { storageObject.setStorageClass(storageClass.toString()); } + if (timeStorageClassUpdated != null) { + storageObject.setTimeStorageClassUpdated(new DateTime(timeStorageClassUpdated)); + } Map pbMetadata = metadata; if (metadata != null && !Data.isNull(metadata)) { @@ -1193,6 +1224,9 @@ public Acl apply(ObjectAccessControl objectAccessControl) { if (storageObject.getStorageClass() != null) { builder.setStorageClass(StorageClass.valueOf(storageObject.getStorageClass())); } + if (storageObject.getTimeStorageClassUpdated() != null) { + builder.setTimeStorageClassUpdated(storageObject.getTimeStorageClassUpdated().getValue()); + } if (storageObject.getKmsKeyName() != null) { builder.setKmsKeyName(storageObject.getKmsKeyName()); } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 83ca9dd44..08eec26a0 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -154,7 +154,8 @@ enum BlobField implements FieldSelector { TEMPORARY_HOLD("temporaryHold"), RETENTION_EXPIRATION_TIME("retentionExpirationTime"), UPDATED("updated"), - CUSTOM_TIME("customTime"); + CUSTOM_TIME("customTime"), + TIME_STORAGE_CLASS_UPDATED("timeStorageClassUpdated"); static final List REQUIRED_FIELDS = ImmutableList.of(BUCKET, NAME); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java index d4119971f..d23e0dc7a 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java @@ -75,6 +75,7 @@ public class BlobInfoTest { private static final String KMS_KEY_NAME = "projects/p/locations/kr-loc/keyRings/kr/cryptoKeys/key"; private static final StorageClass STORAGE_CLASS = StorageClass.COLDLINE; + private static final Long TIME_STORAGE_CLASS_UPDATED = CREATE_TIME; private static final Boolean EVENT_BASED_HOLD = true; private static final Boolean TEMPORARY_HOLD = true; private static final Long RETENTION_EXPIRATION_TIME = 10L; @@ -104,6 +105,7 @@ public class BlobInfoTest { .setCreateTime(CREATE_TIME) .setCustomTime(CUSTOM_TIME) .setStorageClass(STORAGE_CLASS) + .setTimeStorageClassUpdated(TIME_STORAGE_CLASS_UPDATED) .setKmsKeyName(KMS_KEY_NAME) .setEventBasedHold(EVENT_BASED_HOLD) .setTemporaryHold(TEMPORARY_HOLD) @@ -199,6 +201,7 @@ public void testBuilder() { assertEquals(CREATE_TIME, BLOB_INFO.getCreateTime()); assertEquals(CUSTOM_TIME, BLOB_INFO.getCustomTime()); assertEquals(STORAGE_CLASS, BLOB_INFO.getStorageClass()); + assertEquals(TIME_STORAGE_CLASS_UPDATED, BLOB_INFO.getTimeStorageClassUpdated()); assertEquals(KMS_KEY_NAME, BLOB_INFO.getKmsKeyName()); assertEquals(EVENT_BASED_HOLD, BLOB_INFO.getEventBasedHold()); assertEquals(TEMPORARY_HOLD, BLOB_INFO.getTemporaryHold()); @@ -264,6 +267,7 @@ private void compareBlobs(BlobInfo expected, BlobInfo value) { assertEquals(expected.getCustomTime(), value.getCustomTime()); assertEquals(expected.getUpdateTime(), value.getUpdateTime()); assertEquals(expected.getStorageClass(), value.getStorageClass()); + assertEquals(expected.getTimeStorageClassUpdated(), value.getTimeStorageClassUpdated()); assertEquals(expected.getKmsKeyName(), value.getKmsKeyName()); assertEquals(expected.getEventBasedHold(), value.getEventBasedHold()); assertEquals(expected.getTemporaryHold(), value.getTemporaryHold()); @@ -319,6 +323,7 @@ public void testToPbAndFromPb() { assertEquals(0L, (long) blobInfo.getSize()); assertNull(blobInfo.getUpdateTime()); assertNull(blobInfo.getStorageClass()); + assertNull(blobInfo.getTimeStorageClassUpdated()); assertNull(blobInfo.getKmsKeyName()); assertNull(blobInfo.getEventBasedHold()); assertNull(blobInfo.getTemporaryHold()); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java index 2ff21b712..36752a4dc 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java @@ -92,6 +92,8 @@ public class BlobTest { private static final Long UPDATE_TIME = DELETE_TIME - 1L; private static final Long CREATE_TIME = UPDATE_TIME - 1L; private static final Long CUSTOM_TIME = CREATE_TIME - 1L; + private static final StorageClass STORAGE_CLASS = StorageClass.COLDLINE; + private static final Long TIME_STORAGE_CLASS_UPDATED = CREATE_TIME; private static final String ENCRYPTION_ALGORITHM = "AES256"; private static final String KEY_SHA256 = "keySha"; private static final BlobInfo.CustomerEncryption CUSTOMER_ENCRYPTION = @@ -124,6 +126,8 @@ public class BlobTest { .setUpdateTime(UPDATE_TIME) .setCreateTime(CREATE_TIME) .setCustomTime(CUSTOM_TIME) + .setStorageClass(STORAGE_CLASS) + .setTimeStorageClassUpdated(TIME_STORAGE_CLASS_UPDATED) .setCustomerEncryption(CUSTOMER_ENCRYPTION) .setKmsKeyName(KMS_KEY_NAME) .setEventBasedHold(EVENT_BASED_HOLD) @@ -513,6 +517,8 @@ public void testBuilder() { .setCrc32c(CRC32) .setCreateTime(CREATE_TIME) .setCustomTime(CUSTOM_TIME) + .setStorageClass(STORAGE_CLASS) + .setTimeStorageClassUpdated(TIME_STORAGE_CLASS_UPDATED) .setCustomerEncryption(CUSTOMER_ENCRYPTION) .setKmsKeyName(KMS_KEY_NAME) .setEventBasedHold(EVENT_BASED_HOLD) @@ -543,6 +549,8 @@ public void testBuilder() { assertEquals(CRC32_HEX_STRING, blob.getCrc32cToHexString()); assertEquals(CREATE_TIME, blob.getCreateTime()); assertEquals(CUSTOM_TIME, blob.getCustomTime()); + assertEquals(STORAGE_CLASS, blob.getStorageClass()); + assertEquals(TIME_STORAGE_CLASS_UPDATED, blob.getTimeStorageClassUpdated()); assertEquals(CUSTOMER_ENCRYPTION, blob.getCustomerEncryption()); assertEquals(KMS_KEY_NAME, blob.getKmsKeyName()); assertEquals(EVENT_BASED_HOLD, blob.getEventBasedHold()); @@ -576,6 +584,8 @@ public void testBuilder() { assertNull(blob.getCrc32c()); assertNull(blob.getCrc32cToHexString()); assertNull(blob.getCreateTime()); + assertNull(blob.getStorageClass()); + assertNull(blob.getTimeStorageClassUpdated()); assertNull(blob.getCustomerEncryption()); assertNull(blob.getKmsKeyName()); assertNull(blob.getEventBasedHold()); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java index 925e3d6d1..84936e0d4 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java @@ -3567,4 +3567,42 @@ public void testBucketUpdateTime() throws ExecutionException, InterruptedExcepti RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS); } } + + @Test + public void testBlobTimeStorageClassUpdated() { + String blobName = "test-blob-with-storage-class"; + StorageClass storageClass = StorageClass.COLDLINE; + BlobInfo blob = BlobInfo.newBuilder(BUCKET, blobName).setStorageClass(storageClass).build(); + Blob remoteBlob = storage.create(blob); + assertThat(remoteBlob).isNotNull(); + assertThat(remoteBlob.getBucket()).isEqualTo(blob.getBucket()); + assertThat(remoteBlob.getName()).isEqualTo(blob.getName()); + assertThat(remoteBlob.getCreateTime()).isNotNull(); + assertThat(remoteBlob.getUpdateTime()).isEqualTo(remoteBlob.getCreateTime()); + assertThat(remoteBlob.getTimeStorageClassUpdated()).isEqualTo(remoteBlob.getCreateTime()); + + // We can't change an object's storage class directly, the only way is to rewrite the object + // with the desired storage class. + BlobId blobId = BlobId.of(BUCKET, blobName); + Storage.CopyRequest request = + Storage.CopyRequest.newBuilder() + .setSource(blobId) + .setTarget(BlobInfo.newBuilder(blobId).setStorageClass(StorageClass.STANDARD).build()) + .build(); + Blob updatedBlob1 = storage.copy(request).getResult(); + assertThat(updatedBlob1.getTimeStorageClassUpdated()).isNotNull(); + assertThat(updatedBlob1.getCreateTime()).isGreaterThan(remoteBlob.getCreateTime()); + assertThat(updatedBlob1.getUpdateTime()).isGreaterThan(remoteBlob.getCreateTime()); + assertThat(updatedBlob1.getTimeStorageClassUpdated()) + .isGreaterThan(remoteBlob.getTimeStorageClassUpdated()); + + // 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(); + assertThat(updatedBlob2.getUpdateTime()) + .isGreaterThan(updatedBlob2.getTimeStorageClassUpdated()); + assertThat(updatedBlob2.getTimeStorageClassUpdated()) + .isEqualTo(updatedBlob1.getTimeStorageClassUpdated()); + assertThat(updatedBlob2.delete()).isTrue(); + } }