diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java index 7cdf742ef31e..da7e27a72851 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java @@ -333,12 +333,24 @@ public Builder setMd5(String md5) { return this; } + @Override + public Builder setMd5FromHexString(String md5HexString) { + infoBuilder.setMd5FromHexString(md5HexString); + return this; + } + @Override public Builder setCrc32c(String crc32c) { infoBuilder.setCrc32c(crc32c); return this; } + @Override + public Builder setCrc32cFromHexString(String crc32cHexString) { + infoBuilder.setCrc32cFromHexString(crc32cHexString); + return this; + } + @Override Builder setMediaLink(String mediaLink) { infoBuilder.setMediaLink(mediaLink); diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java index 2ace22d6f9f1..d1bea35a9487 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java @@ -25,16 +25,17 @@ import com.google.api.services.storage.model.ObjectAccessControl; import com.google.api.services.storage.model.StorageObject; import com.google.api.services.storage.model.StorageObject.Owner; -import com.google.cloud.storage.Blob.Builder; import com.google.common.base.Function; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.io.BaseEncoding; import java.io.Serializable; import java.math.BigInteger; import java.util.AbstractMap; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -226,6 +227,14 @@ public abstract static class Builder { */ public abstract Builder setMd5(String md5); + /** + * Sets the MD5 hash of blob's data from hex string. + * + * @see Hashes and ETags: + * Best Practices + */ + public abstract Builder setMd5FromHexString(String md5HexString); + /** * Sets the CRC32C checksum of blob's data as described in RFC 4960, Appendix B; encoded in @@ -236,6 +245,16 @@ public abstract static class Builder { */ public abstract Builder setCrc32c(String crc32c); + /** + * Sets the CRC32C checksum of blob's data as described in RFC 4960, Appendix B; from hex + * string. + * + * @see Hashes and ETags: + * Best Practices + */ + public abstract Builder setCrc32cFromHexString(String crc32cHexString); + abstract Builder setMediaLink(String mediaLink); /** Sets the blob's storage class. */ @@ -423,12 +442,39 @@ public Builder setMd5(String md5) { return this; } + public Builder setMd5FromHexString(String md5HexString) { + if (md5HexString == null) { + return this; + } + byte[] bytes = new BigInteger(md5HexString, 16).toByteArray(); + int leadingEmptyBytes = bytes.length - md5HexString.length() / 2; + if (leadingEmptyBytes > 0) { + bytes = Arrays.copyOfRange(bytes, leadingEmptyBytes, bytes.length); + } + this.md5 = BaseEncoding.base64().encode(bytes); + return this; + } + @Override public Builder setCrc32c(String crc32c) { this.crc32c = firstNonNull(crc32c, Data.nullOf(String.class)); return this; } + @Override + public Builder setCrc32cFromHexString(String crc32cHexString) { + if (crc32cHexString == null) { + return this; + } + byte[] bytes = new BigInteger(crc32cHexString, 16).toByteArray(); + int leadingEmptyBytes = bytes.length - crc32cHexString.length() / 2; + if (leadingEmptyBytes > 0) { + bytes = Arrays.copyOfRange(bytes, leadingEmptyBytes, bytes.length); + } + this.crc32c = BaseEncoding.base64().encode(bytes); + return this; + } + @Override Builder setMediaLink(String mediaLink) { this.mediaLink = mediaLink; @@ -674,6 +720,24 @@ public String getMd5() { return Data.isNull(md5) ? null : md5; } + /** + * Returns the MD5 hash of blob's data decoded to string. + * + * @see Hashes and ETags: + * Best Practices + */ + public String getMd5ToHexString() { + if (md5 == null) { + return null; + } + byte[] decodedMd5 = BaseEncoding.base64().decode(md5); + StringBuilder stringBuilder = new StringBuilder(); + for (byte b : decodedMd5) { + stringBuilder.append(String.format("%02x", b & 0xff)); + } + return stringBuilder.toString(); + } + /** * Returns the CRC32C checksum of blob's data as described in RFC 4960, Appendix B; encoded in @@ -686,6 +750,26 @@ public String getCrc32c() { return Data.isNull(crc32c) ? null : crc32c; } + /** + * Returns the CRC32C checksum of blob's data as described in RFC 4960, Appendix B; decoded to + * string. + * + * @see Hashes and ETags: + * Best Practices + */ + public String getCrc32cToHexString() { + if (crc32c == null) { + return null; + } + byte[] decodeCrc32c = BaseEncoding.base64().decode(crc32c); + StringBuilder stringBuilder = new StringBuilder(); + for (byte b : decodeCrc32c) { + stringBuilder.append(String.format("%02x", b & 0xff)); + } + return stringBuilder.toString(); + } + /** Returns the blob's media download link. */ public String getMediaLink() { return mediaLink; diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java index aea38d161f9a..a57be6df6358 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java @@ -47,12 +47,14 @@ public class BlobInfoTest { private static final String CONTENT_DISPOSITION = "content-disposition"; private static final String CONTENT_ENCODING = "UTF-8"; private static final String CONTENT_LANGUAGE = "En"; - private static final String CRC32 = "0xFF00"; + private static final String CRC32 = "FF00"; + private static final String CRC32_HEX_STRING = "145d34"; private static final Long DELETE_TIME = System.currentTimeMillis(); private static final String ETAG = "0xFF00"; private static final Long GENERATION = 1L; private static final String GENERATED_ID = "B/N:1"; - private static final String MD5 = "0xFF00"; + private static final String MD5 = "FF00"; + private static final String MD5_HEX_STRING = "145d34"; private static final String MEDIA_LINK = "http://media/b/n"; private static final Map METADATA = ImmutableMap.of("n1", "v1", "n2", "v2"); private static final Long META_GENERATION = 10L; @@ -123,6 +125,20 @@ public void testToBuilder() { compareBlobs(BLOB_INFO, blobInfo); } + @Test + public void testToBuilderSetMd5FromHexString() { + BlobInfo blobInfo = + BlobInfo.newBuilder(BlobId.of("b2", "n2")).setMd5FromHexString(MD5_HEX_STRING).build(); + assertEquals(MD5, blobInfo.getMd5()); + } + + @Test + public void testToBuilderSetCrc32cFromHexString() { + BlobInfo blobInfo = + BlobInfo.newBuilder(BlobId.of("b2", "n2")).setCrc32cFromHexString(CRC32_HEX_STRING).build(); + assertEquals(CRC32, blobInfo.getCrc32c()); + } + @Test public void testToBuilderIncomplete() { BlobInfo incompleteBlobInfo = BlobInfo.newBuilder(BlobId.of("b2", "n2")).build(); @@ -142,11 +158,13 @@ public void testBuilder() { assertEquals(CONTENT_LANGUAGE, BLOB_INFO.getContentLanguage()); assertEquals(CUSTOMER_ENCRYPTION, BLOB_INFO.getCustomerEncryption()); assertEquals(CRC32, BLOB_INFO.getCrc32c()); + assertEquals(CRC32_HEX_STRING, BLOB_INFO.getCrc32cToHexString()); assertEquals(DELETE_TIME, BLOB_INFO.getDeleteTime()); assertEquals(ETAG, BLOB_INFO.getEtag()); assertEquals(GENERATION, BLOB_INFO.getGeneration()); assertEquals(GENERATED_ID, BLOB_INFO.getGeneratedId()); assertEquals(MD5, BLOB_INFO.getMd5()); + assertEquals(MD5_HEX_STRING, BLOB_INFO.getMd5ToHexString()); assertEquals(MEDIA_LINK, BLOB_INFO.getMediaLink()); assertEquals(METADATA, BLOB_INFO.getMetadata()); assertEquals(META_GENERATION, BLOB_INFO.getMetageneration()); @@ -172,12 +190,14 @@ public void testBuilder() { assertNull(DIRECTORY_INFO.getContentLanguage()); assertNull(DIRECTORY_INFO.getCustomerEncryption()); assertNull(DIRECTORY_INFO.getCrc32c()); + assertNull(DIRECTORY_INFO.getCrc32cToHexString()); assertNull(DIRECTORY_INFO.getCreateTime()); assertNull(DIRECTORY_INFO.getDeleteTime()); assertNull(DIRECTORY_INFO.getEtag()); assertNull(DIRECTORY_INFO.getGeneration()); assertNull(DIRECTORY_INFO.getGeneratedId()); assertNull(DIRECTORY_INFO.getMd5()); + assertNull(DIRECTORY_INFO.getMd5ToHexString()); assertNull(DIRECTORY_INFO.getMediaLink()); assertNull(DIRECTORY_INFO.getMetadata()); assertNull(DIRECTORY_INFO.getMetageneration()); @@ -201,12 +221,14 @@ private void compareBlobs(BlobInfo expected, BlobInfo value) { assertEquals(expected.getContentLanguage(), value.getContentLanguage()); assertEquals(expected.getCustomerEncryption(), value.getCustomerEncryption()); assertEquals(expected.getCrc32c(), value.getCrc32c()); + assertEquals(expected.getCrc32cToHexString(), value.getCrc32cToHexString()); assertEquals(expected.getCreateTime(), value.getCreateTime()); assertEquals(expected.getDeleteTime(), value.getDeleteTime()); assertEquals(expected.getEtag(), value.getEtag()); assertEquals(expected.getGeneration(), value.getGeneration()); assertEquals(expected.getGeneratedId(), value.getGeneratedId()); assertEquals(expected.getMd5(), value.getMd5()); + assertEquals(expected.getMd5ToHexString(), value.getMd5ToHexString()); assertEquals(expected.getMediaLink(), value.getMediaLink()); assertEquals(expected.getMetadata(), value.getMetadata()); assertEquals(expected.getMetageneration(), value.getMetageneration()); @@ -253,12 +275,14 @@ public void testToPbAndFromPb() { assertNull(blobInfo.getContentLanguage()); assertNull(blobInfo.getCustomerEncryption()); assertNull(blobInfo.getCrc32c()); + assertNull(blobInfo.getCrc32cToHexString()); assertNull(blobInfo.getCreateTime()); assertNull(blobInfo.getDeleteTime()); assertNull(blobInfo.getEtag()); assertNull(blobInfo.getGeneration()); assertNull(blobInfo.getGeneratedId()); assertNull(blobInfo.getMd5()); + assertNull(blobInfo.getMd5ToHexString()); assertNull(blobInfo.getMediaLink()); assertNull(blobInfo.getMetadata()); assertNull(blobInfo.getMetageneration()); diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java index ff517d360dd9..d68bf965f1f4 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java @@ -70,12 +70,14 @@ public class BlobTest { private static final String CONTENT_DISPOSITION = "content-disposition"; private static final String CONTENT_ENCODING = "UTF-8"; private static final String CONTENT_LANGUAGE = "En"; - private static final String CRC32 = "0xFF00"; + private static final String CRC32 = "FF00"; + private static final String CRC32_HEX_STRING = "145d34"; private static final Long DELETE_TIME = System.currentTimeMillis(); private static final String ETAG = "0xFF00"; private static final Long GENERATION = 1L; private static final String GENERATED_ID = "B/N:1"; - private static final String MD5 = "0xFF00"; + private static final String MD5 = "FF00"; + private static final String MD5_HEX_STRING = "145d34"; private static final String MEDIA_LINK = "http://media/b/n"; private static final Map METADATA = ImmutableMap.of("n1", "v1", "n2", "v2"); private static final Long META_GENERATION = 10L; @@ -506,6 +508,7 @@ public void testBuilder() { assertEquals(CONTENT_ENCODING, blob.getContentEncoding()); assertEquals(CONTENT_LANGUAGE, blob.getContentLanguage()); assertEquals(CRC32, blob.getCrc32c()); + assertEquals(CRC32_HEX_STRING, blob.getCrc32cToHexString()); assertEquals(CREATE_TIME, blob.getCreateTime()); assertEquals(CUSTOMER_ENCRYPTION, blob.getCustomerEncryption()); assertEquals(KMS_KEY_NAME, blob.getKmsKeyName()); @@ -516,6 +519,7 @@ public void testBuilder() { assertEquals(ETAG, blob.getEtag()); assertEquals(GENERATED_ID, blob.getGeneratedId()); assertEquals(MD5, blob.getMd5()); + assertEquals(MD5_HEX_STRING, blob.getMd5ToHexString()); assertEquals(MEDIA_LINK, blob.getMediaLink()); assertEquals(METADATA, blob.getMetadata()); assertEquals(META_GENERATION, blob.getMetageneration()); @@ -537,6 +541,7 @@ public void testBuilder() { assertNull(blob.getContentEncoding()); assertNull(blob.getContentLanguage()); assertNull(blob.getCrc32c()); + assertNull(blob.getCrc32cToHexString()); assertNull(blob.getCreateTime()); assertNull(blob.getCustomerEncryption()); assertNull(blob.getKmsKeyName()); @@ -547,6 +552,7 @@ public void testBuilder() { assertNull(blob.getEtag()); assertNull(blob.getGeneratedId()); assertNull(blob.getMd5()); + assertNull(blob.getMd5ToHexString()); assertNull(blob.getMediaLink()); assertNull(blob.getMetadata()); assertNull(blob.getMetageneration()); diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java index 3a97dab2800d..900ba462f705 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java @@ -460,6 +460,26 @@ public void testCreateBlob() { assertTrue(remoteBlob.delete()); } + @Test + public void testCreateBlobMd5Crc32cFromHexString() { + String blobName = "test-create-blob-md5-crc32c-from-hex-string"; + BlobInfo blob = + BlobInfo.newBuilder(BUCKET, blobName) + .setContentType(CONTENT_TYPE) + .setMd5FromHexString("3b54781b51c94835084898e821899585") + .setCrc32cFromHexString("f4ddc43d") + .build(); + Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT); + assertNotNull(remoteBlob); + assertEquals(blob.getBucket(), remoteBlob.getBucket()); + assertEquals(blob.getName(), remoteBlob.getName()); + assertEquals(blob.getMd5ToHexString(), remoteBlob.getMd5ToHexString()); + assertEquals(blob.getCrc32cToHexString(), remoteBlob.getCrc32cToHexString()); + byte[] readBytes = storage.readAllBytes(BUCKET, blobName); + assertArrayEquals(BLOB_BYTE_CONTENT, readBytes); + assertTrue(remoteBlob.delete()); + } + @Test public void testCreateBlobWithEncryptionKey() { String blobName = "test-create-with-customer-key-blob"; diff --git a/google-cloud-examples/src/main/java/com/google/cloud/examples/storage/snippets/StorageSnippets.java b/google-cloud-examples/src/main/java/com/google/cloud/examples/storage/snippets/StorageSnippets.java index ab2ec4df8edf..30a603c4b66c 100644 --- a/google-cloud-examples/src/main/java/com/google/cloud/examples/storage/snippets/StorageSnippets.java +++ b/google-cloud-examples/src/main/java/com/google/cloud/examples/storage/snippets/StorageSnippets.java @@ -1136,11 +1136,13 @@ public void getBlobMetadata(String bucketName, String blobName) throws StorageEx System.out.println("ContentLanguage: " + blob.getContentLanguage()); System.out.println("ContentType: " + blob.getContentType()); System.out.println("Crc32c: " + blob.getCrc32c()); + System.out.println("Crc32cHexString: " + blob.getCrc32cToHexString()); System.out.println("ETag: " + blob.getEtag()); System.out.println("Generation: " + blob.getGeneration()); System.out.println("Id: " + blob.getBlobId()); System.out.println("KmsKeyName: " + blob.getKmsKeyName()); System.out.println("Md5Hash: " + blob.getMd5()); + System.out.println("Md5HexString: " + blob.getMd5ToHexString()); System.out.println("MediaLink: " + blob.getMediaLink()); System.out.println("Metageneration: " + blob.getMetageneration()); System.out.println("Name: " + blob.getName()); diff --git a/google-cloud-examples/src/test/java/com/google/cloud/examples/storage/snippets/ITStorageSnippets.java b/google-cloud-examples/src/test/java/com/google/cloud/examples/storage/snippets/ITStorageSnippets.java index 19db91dcf44b..183b68679775 100644 --- a/google-cloud-examples/src/test/java/com/google/cloud/examples/storage/snippets/ITStorageSnippets.java +++ b/google-cloud-examples/src/test/java/com/google/cloud/examples/storage/snippets/ITStorageSnippets.java @@ -458,11 +458,13 @@ public void testGetBlobMetadata() { assertTrue(snippetOutput.contains("ContentLanguage: " + remoteBlob.getContentLanguage())); assertTrue(snippetOutput.contains("ContentType: " + remoteBlob.getContentType())); assertTrue(snippetOutput.contains("Crc32c: " + remoteBlob.getCrc32c())); + assertTrue(snippetOutput.contains("Crc32cHexString: " + remoteBlob.getCrc32cToHexString())); assertTrue(snippetOutput.contains("ETag: " + remoteBlob.getEtag())); assertTrue(snippetOutput.contains("Generation: " + remoteBlob.getGeneration())); assertTrue(snippetOutput.contains("Id: " + remoteBlob.getBlobId())); assertTrue(snippetOutput.contains("KmsKeyName: " + remoteBlob.getKmsKeyName())); assertTrue(snippetOutput.contains("Md5Hash: " + remoteBlob.getMd5())); + assertTrue(snippetOutput.contains("Md5HexString: " + remoteBlob.getMd5ToHexString())); assertTrue(snippetOutput.contains("MediaLink: " + remoteBlob.getMediaLink())); assertTrue(snippetOutput.contains("Metageneration: " + remoteBlob.getMetageneration())); assertTrue(snippetOutput.contains("Name: " + remoteBlob.getName()));