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()));