From e10dde908e3231aeebf477d5a9a62f43562cf7d8 Mon Sep 17 00:00:00 2001 From: andrey-qlogic Date: Thu, 24 Jan 2019 18:32:17 +0000 Subject: [PATCH 1/4] 4273: Added a new create method to pass offset and lenght of sub array. --- .../com/google/cloud/storage/Storage.java | 23 ++++++++++++++ .../com/google/cloud/storage/StorageImpl.java | 16 ++++++++++ .../google/cloud/storage/StorageImplTest.java | 31 +++++++++++++++++++ .../storage/snippets/StorageSnippets.java | 13 ++++++++ 4 files changed, 83 insertions(+) diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 6ce9ca8c6e2a..6c936a61ed8b 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -1429,6 +1429,29 @@ public static Builder newBuilder() { */ Blob create(BlobInfo blobInfo, byte[] content, BlobTargetOption... options); + /** + * Creates a new blob with the sub array of the given byte array. Direct upload is used to upload + * {@code content}. For large content, {@link #writer} is recommended as it uses resumable upload. + * MD5 and CRC32C hashes of {@code content} are computed and used for validating transferred data. + * Accepts an optional userProject {@link BlobGetOption} option which defines the project id to + * assign operational costs. + * + *

Example of creating a blob from a byte array. + * + *

{@code
+   * String bucketName = "my_unique_bucket";
+   * String blobName = "my_blob_name";
+   * BlobId blobId = BlobId.of(bucketName, blobName);
+   * BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build();
+   * Blob blob = storage.create(blobInfo, "Hello, World!".getBytes(UTF_8), 7, 5);
+   * }
+ * + * @return a [@code Blob} with complete information + * @throws StorageException upon failure + * @see Hashes and ETags + */ + Blob create(BlobInfo blobInfo, byte[] content, int offset, int length, BlobTargetOption... options); + /** * Creates a new blob. Direct upload is used to upload {@code content}. For large content, {@link * #writer} is recommended as it uses resumable upload. By default any md5 and crc32c values in diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index ec9fdffbca5e..ac4da4c32eea 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -149,6 +149,22 @@ public Blob create(BlobInfo blobInfo, byte[] content, BlobTargetOption... option return internalCreate(updatedInfo, content, options); } + @Override + public Blob create( + BlobInfo blobInfo, byte[] content, int offset, int length, BlobTargetOption... options) { + content = firstNonNull(content, EMPTY_BYTE_ARRAY); + byte[] subContent = Arrays.copyOfRange(content, offset, offset + length); + BlobInfo updatedInfo = + blobInfo + .toBuilder() + .setMd5(BaseEncoding.base64().encode(Hashing.md5().hashBytes(subContent).asBytes())) + .setCrc32c( + BaseEncoding.base64() + .encode(Ints.toByteArray(Hashing.crc32c().hashBytes(subContent).asInt()))) + .build(); + return internalCreate(updatedInfo, subContent, options); + } + @Override @Deprecated public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) { diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java index 393c7ff3bfcf..ef1fb677edcd 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java @@ -97,8 +97,11 @@ public class StorageImplTest { private static final String BLOB_NAME2 = "n2"; private static final String BLOB_NAME3 = "n3"; private static final byte[] BLOB_CONTENT = {0xD, 0xE, 0xA, 0xD}; + private static final byte[] BLOB_SUB_CONTENT = {0xE, 0xA}; private static final String CONTENT_MD5 = "O1R4G1HJSDUISJjoIYmVhQ=="; private static final String CONTENT_CRC32C = "9N3EPQ=="; + private static final String SUB_CONTENT_MD5 = "5e7c7CdasUiOn3BO560jPg=="; + private static final String SUB_CONTENT_CRC32C = "bljNYA=="; private static final int DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024; private static final String BASE64_KEY = "JVzfVl8NLD9FjedFuStegjRfES5ll5zc59CIXw572OA="; private static final Key KEY = @@ -445,6 +448,34 @@ public void testCreateBlob() throws IOException { assertEquals(-1, byteStream.read(streamBytes)); } + @Test + public void testCreateBlobWithSubArrayFromByteArray() throws IOException { + Capture capturedStream = Capture.newInstance(); + EasyMock.expect( + storageRpcMock.create( + EasyMock.eq( + BLOB_INFO1 + .toBuilder() + .setMd5(SUB_CONTENT_MD5) + .setCrc32c(SUB_CONTENT_CRC32C) + .build() + .toPb()), + EasyMock.capture(capturedStream), + EasyMock.eq(EMPTY_RPC_OPTIONS))) + .andReturn(BLOB_INFO1.toPb()); + EasyMock.replay(storageRpcMock); + initializeService(); + + Blob blob = storage.create(BLOB_INFO1, BLOB_CONTENT, 1, 2); + + assertEquals(expectedBlob1, blob); + ByteArrayInputStream byteStream = capturedStream.getValue(); + byte[] streamBytes = new byte[BLOB_SUB_CONTENT.length]; + assertEquals(BLOB_SUB_CONTENT.length, byteStream.read(streamBytes)); + assertArrayEquals(BLOB_SUB_CONTENT, streamBytes); + assertEquals(-1, byteStream.read(streamBytes)); + } + @Test public void testCreateBlobRetry() throws IOException { Capture capturedStream1 = Capture.newInstance(); 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..cdcdf2ce12c9 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 @@ -132,6 +132,19 @@ public Blob createBlobFromByteArray(String bucketName, String blobName) { return blob; } + /** Example of creating a blob with sub array from a byte array. */ + // [TARGET create(BlobInfo, byte[], offset, length, BlobTargetOption...)] + // [VARIABLE "my_unique_bucket"] + // [VARIABLE "my_blob_name"] + public Blob createBlobWithSubArrayFromByteArray(String bucketName, String blobName) { + // [START createBlobWithSubArrayFromByteArray] + BlobId blobId = BlobId.of(bucketName, blobName); + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build(); + Blob blob = storage.create(blobInfo, "Hello, World!".getBytes(UTF_8), 7, 5); + // [END createBlobWithSubArrayFromByteArray] + return blob; + } + /** Example of creating a blob from an input stream. */ // [TARGET create(BlobInfo, InputStream, BlobWriteOption...)] // [VARIABLE "my_unique_bucket"] From b4a15fc28580aaeae223f34e03e52cd52e9aeb24 Mon Sep 17 00:00:00 2001 From: andrey-qlogic Date: Thu, 24 Jan 2019 20:10:42 +0000 Subject: [PATCH 2/4] 4273: Fixed codeformat error. --- .../src/main/java/com/google/cloud/storage/Storage.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 6c936a61ed8b..eebed6d5ccdb 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -1450,7 +1450,8 @@ public static Builder newBuilder() { * @throws StorageException upon failure * @see Hashes and ETags */ - Blob create(BlobInfo blobInfo, byte[] content, int offset, int length, BlobTargetOption... options); + Blob create( + BlobInfo blobInfo, byte[] content, int offset, int length, BlobTargetOption... options); /** * Creates a new blob. Direct upload is used to upload {@code content}. For large content, {@link From a107d271db61f29dbfca40cc4c516c2921b6380f Mon Sep 17 00:00:00 2001 From: andrey-qlogic Date: Thu, 24 Jan 2019 20:56:07 +0000 Subject: [PATCH 3/4] 4273: Rephrased a comment. --- .../src/main/java/com/google/cloud/storage/Storage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index eebed6d5ccdb..8e1b10a0efd5 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -1433,8 +1433,8 @@ public static Builder newBuilder() { * Creates a new blob with the sub array of the given byte array. Direct upload is used to upload * {@code content}. For large content, {@link #writer} is recommended as it uses resumable upload. * MD5 and CRC32C hashes of {@code content} are computed and used for validating transferred data. - * Accepts an optional userProject {@link BlobGetOption} option which defines the project id to - * assign operational costs. + * Accepts a userProject {@link BlobGetOption} option, which defines the project id to assign + * operational costs. * *

Example of creating a blob from a byte array. * From b0cd8687b03072d5f2003a9c58b4742e56f64667 Mon Sep 17 00:00:00 2001 From: andrey-qlogic Date: Wed, 6 Feb 2019 12:18:04 +0000 Subject: [PATCH 4/4] 4273: Added a new integration test using the new createBlobWithSubArrayFromByteArray code snippet. --- .../storage/snippets/StorageSnippets.java | 5 +++-- .../storage/snippets/ITStorageSnippets.java | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) 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 cdcdf2ce12c9..a46967dabea5 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 @@ -136,11 +136,12 @@ public Blob createBlobFromByteArray(String bucketName, String blobName) { // [TARGET create(BlobInfo, byte[], offset, length, BlobTargetOption...)] // [VARIABLE "my_unique_bucket"] // [VARIABLE "my_blob_name"] - public Blob createBlobWithSubArrayFromByteArray(String bucketName, String blobName) { + public Blob createBlobWithSubArrayFromByteArray( + String bucketName, String blobName, int offset, int length) { // [START createBlobWithSubArrayFromByteArray] BlobId blobId = BlobId.of(bucketName, blobName); BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build(); - Blob blob = storage.create(blobInfo, "Hello, World!".getBytes(UTF_8), 7, 5); + Blob blob = storage.create(blobInfo, "Hello, World!".getBytes(UTF_8), offset, length); // [END createBlobWithSubArrayFromByteArray] return blob; } 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..1a5d752a0a6c 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 @@ -206,6 +206,24 @@ public void testCreateCopyAndGetBlob() { copiedBlob.delete(); } + @Test + public void testCreateCopyAndGetBlobFromSubArray() { + String blobName = "test-create-copy-get-blob-from-sub-array"; + Blob blob = storageSnippets.createBlobWithSubArrayFromByteArray(BUCKET, blobName, 7, 1); + assertNotNull(blob); + Blob copiedBlob = storageSnippets.copyBlobInChunks(BUCKET, blobName, "copy-blob"); + assertNotNull(copiedBlob); + try { + storageSnippets.getBlobFromIdWithMetageneration(BUCKET, blobName, -1); + fail("Expected StorageException to be thrown"); + } catch (StorageException ex) { + // expected + } + assertTrue( + storageSnippets.deleteBlobFromIdWithGeneration(BUCKET, blobName, blob.getGeneration())); + copiedBlob.delete(); + } + @Test public void testCreateBlobFromInputStream() { Blob blob =