From 333a3bb13bdf1714e43aea78085fcb29d8488dd9 Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Thu, 22 Oct 2015 13:14:42 +0200 Subject: [PATCH 1/6] Add null value (empty map) for blob metadata --- .../com/google/gcloud/storage/BlobInfo.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java index 8e6921bbc20d..841bcc5d3b95 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java @@ -28,13 +28,16 @@ import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import java.io.Serializable; import java.math.BigInteger; +import java.util.AbstractMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; /** * Google Storage object metadata. @@ -81,6 +84,14 @@ public StorageObject apply(BlobInfo blobInfo) { private final String contentLanguage; private final Integer componentCount; + public static final class ImmutableEmptyMap extends AbstractMap { + + @Override + public Set> entrySet() { + return ImmutableSet.of(); + } + } + public static final class Builder { private BlobId blobId; @@ -99,7 +110,7 @@ public static final class Builder { private String md5; private String crc32c; private String mediaLink; - private ImmutableMap metadata; + private Map metadata; private Long generation; private Long metageneration; private Long deleteTime; @@ -188,7 +199,8 @@ Builder mediaLink(String mediaLink) { } public Builder metadata(Map metadata) { - this.metadata = metadata != null ? ImmutableMap.copyOf(metadata) : null; + this.metadata = metadata != null ? + ImmutableMap.copyOf(metadata) : Data.nullOf(ImmutableEmptyMap.class); return this; } @@ -315,7 +327,7 @@ public String mediaLink() { } public Map metadata() { - return metadata; + return Data.isNull(metadata) ? null : metadata; } public Long generation() { From 3b917417d6ca9b0cee5a162955755d8a35d1aaa0 Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Thu, 22 Oct 2015 13:15:32 +0200 Subject: [PATCH 2/6] Document patch behaviour in Blob and Storage javadoc --- .../java/com/google/gcloud/storage/Blob.java | 24 ++++++++++++++----- .../com/google/gcloud/storage/Storage.java | 24 ++++++++++++++++--- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Blob.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Blob.java index a4a817ead2df..bc75408997f4 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Blob.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Blob.java @@ -190,6 +190,16 @@ public Blob reload(BlobSourceOption... options) { * made on the metadata generation of the current blob. If you want to update the information only * if the current blob metadata are at their latest version use the {@code metagenerationMatch} * option: {@code blob.update(newInfo, BlobTargetOption.metagenerationMatch())}. + *

+ * Original metadata are merged with metadata in the provided {@code blobInfo}. To replace + * metadata instead you first have to unset them. Unsetting metadata can be done by setting the + * provided {@code blobInfo}'s metadata to {@code null}. + *

+ *

+ * Example usage of replacing blob's metadata: + *

    {@code blob.update(blob.info().toBuilder().metadata(null).build());}
+   *    {@code blob.update(blob.info().toBuilder().metadata(newMetadata).build());}
+   * 
* * @param blobInfo new blob's information. Bucket and blob names must match the current ones * @param options update options @@ -306,8 +316,7 @@ public Storage storage() { } /** - * Gets the requested blobs. If {@code infos.length == 0} an empty list is returned. If - * {@code infos.length > 1} a batch request is used to fetch blobs. + * Gets the requested blobs. A batch request is used to fetch blobs. * * @param storage the storage service used to issue the request * @param blobs the blobs to get @@ -331,8 +340,12 @@ public Blob apply(BlobInfo f) { } /** - * Updates the requested blobs. If {@code infos.length == 0} an empty list is returned. If - * {@code infos.length > 1} a batch request is used to update blobs. + * Updates the requested blobs. A batch request is used to update blobs. Original metadata are + * merged with metadata in the provided {@code BlobInfo} objects. To replace metadata instead + * you first have to unset them. Unsetting metadata can be done by setting the provided + * {@code BlobInfo} objects metadata to {@code null}. See + * {@link #update(com.google.gcloud.storage.BlobInfo, + * com.google.gcloud.storage.Storage.BlobTargetOption...) } for a code example. * * @param storage the storage service used to issue the request * @param infos the blobs to update @@ -356,8 +369,7 @@ public Blob apply(BlobInfo f) { } /** - * Deletes the requested blobs. If {@code infos.length == 0} an empty list is returned. If - * {@code infos.length > 1} a batch request is used to delete blobs. + * Deletes the requested blobs. A batch request is used to delete blobs. * * @param storage the storage service used to issue the request * @param blobs the blobs to delete diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java index f9a1c00d4bec..267bcdf5b24c 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Storage.java @@ -683,7 +683,14 @@ public static Builder builder() { BucketInfo update(BucketInfo bucketInfo, BucketTargetOption... options); /** - * Update blob information. + * Update blob information. Original metadata are merged with metadata in the provided + * {@code blobInfo}. To replace metadata instead you first have to unset them. Unsetting metadata + * can be done by setting the provided {@code blobInfo}'s metadata to {@code null}. + *

+ * Example usage of replacing blob's metadata: + *

    {@code service.update(BlobInfo.builder("bucket", "name").metadata(null).build());}
+   *    {@code service.update(BlobInfo.builder("bucket", "name").metadata(newMetadata).build());}
+   * 
* * @return the updated blob * @throws StorageException upon failure @@ -691,7 +698,14 @@ public static Builder builder() { BlobInfo update(BlobInfo blobInfo, BlobTargetOption... options); /** - * Update blob information. + * Update blob information. Original metadata are merged with metadata in the provided + * {@code blobInfo}. To replace metadata instead you first have to unset them. Unsetting metadata + * can be done by setting the provided {@code blobInfo}'s metadata to {@code null}. + *

+ * Example usage of replacing blob's metadata: + *

    {@code service.update(BlobInfo.builder("bucket", "name").metadata(null).build());}
+   *    {@code service.update(BlobInfo.builder("bucket", "name").metadata(newMetadata).build());}
+   * 
* * @return the updated blob * @throws StorageException upon failure @@ -826,7 +840,11 @@ public static Builder builder() { List get(BlobId... blobIds); /** - * Updates the requested blobs. A batch request is used to perform this call. + * Updates the requested blobs. A batch request is used to perform this call. Original metadata + * are merged with metadata in the provided {@code BlobInfo} objects. To replace metadata instead + * you first have to unset them. Unsetting metadata can be done by setting the provided + * {@code BlobInfo} objects metadata to {@code null}. See + * {@link #update(com.google.gcloud.storage.BlobInfo)} for a code example. * * @param blobInfos blobs to update * @return an immutable list of {@code BlobInfo} objects. If a blob does not exist or access to it From 99178a3d0758a2ad7e1577c7e3adc99b8ef542bb Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Thu, 22 Oct 2015 13:20:47 +0200 Subject: [PATCH 3/6] Add integration test for replacing metadata --- .../google/gcloud/storage/ITStorageTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java index 2747444d1f27..36ea2041683a 100644 --- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java +++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.gcloud.RestorableState; import com.google.gcloud.storage.testing.RemoteGcsHelper; @@ -174,6 +175,23 @@ public void testUpdateBlob() { assertTrue(storage.delete(bucket, blobName)); } + @Test + public void testUpdateBlobReplaceMetadata() { + String blobName = "test-update-blob-replace-metadata"; + BlobInfo blob = BlobInfo.builder(bucket, blobName) + .contentType(CONTENT_TYPE) + .metadata(ImmutableMap.of("k1", "a")) + .build(); + assertNotNull(storage.create(blob)); + BlobInfo updatedBlob = storage.update(blob.toBuilder().metadata(null).build()); + assertNotNull(updatedBlob); + assertNull(updatedBlob.metadata()); + updatedBlob = storage.update(blob.toBuilder().metadata(ImmutableMap.of("k2", "b")).build()); + assertEquals(blob.blobId(), updatedBlob.blobId()); + assertEquals(ImmutableMap.of("k2", "b"), updatedBlob.metadata()); + assertTrue(storage.delete(bucket, blobName)); + } + @Test public void testUpdateBlobFail() { String blobName = "test-update-blob-fail"; From 04cf0a7c708cbf8c0e53fb971490b0e2cc6c5ab5 Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Thu, 22 Oct 2015 13:22:00 +0200 Subject: [PATCH 4/6] Minor refactoring of ITStorageTest --- .../google/gcloud/storage/ITStorageTest.java | 47 +++++++------------ 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java index 36ea2041683a..20fdc84ff8a7 100644 --- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java +++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java @@ -96,8 +96,7 @@ public void testCreateBlob() { BlobInfo blob = BlobInfo.builder(bucket, blobName).build(); BlobInfo remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT); assertNotNull(remoteBlob); - assertEquals(blob.bucket(), remoteBlob.bucket()); - assertEquals(blob.name(), remoteBlob.name()); + assertEquals(blob.blobId(), remoteBlob.blobId()); byte[] readBytes = storage.readAllBytes(bucket, blobName); assertArrayEquals(BLOB_BYTE_CONTENT, readBytes); assertTrue(storage.delete(bucket, blobName)); @@ -109,8 +108,7 @@ public void testCreateEmptyBlob() { BlobInfo blob = BlobInfo.builder(bucket, blobName).build(); BlobInfo remoteBlob = storage.create(blob); assertNotNull(remoteBlob); - assertEquals(blob.bucket(), remoteBlob.bucket()); - assertEquals(blob.name(), remoteBlob.name()); + assertEquals(blob.blobId(), remoteBlob.blobId()); byte[] readBytes = storage.readAllBytes(bucket, blobName); assertArrayEquals(new byte[0], readBytes); assertTrue(storage.delete(bucket, blobName)); @@ -123,8 +121,7 @@ public void testCreateBlobStream() throws UnsupportedEncodingException { ByteArrayInputStream stream = new ByteArrayInputStream(BLOB_STRING_CONTENT.getBytes(UTF_8)); BlobInfo remoteBlob = storage.create(blob, stream); assertNotNull(remoteBlob); - assertEquals(blob.bucket(), remoteBlob.bucket()); - assertEquals(blob.name(), remoteBlob.name()); + assertEquals(blob.blobId(), remoteBlob.blobId()); assertEquals(blob.contentType(), remoteBlob.contentType()); byte[] readBytes = storage.readAllBytes(bucket, blobName); assertEquals(BLOB_STRING_CONTENT, new String(readBytes, UTF_8)); @@ -169,8 +166,7 @@ public void testUpdateBlob() { assertNotNull(storage.create(blob)); BlobInfo updatedBlob = storage.update(blob.toBuilder().contentType(CONTENT_TYPE).build()); assertNotNull(updatedBlob); - assertEquals(blob.bucket(), updatedBlob.bucket()); - assertEquals(blob.name(), updatedBlob.name()); + assertEquals(blob.blobId(), updatedBlob.blobId()); assertEquals(CONTENT_TYPE, updatedBlob.contentType()); assertTrue(storage.delete(bucket, blobName)); } @@ -241,8 +237,7 @@ public void testComposeBlob() { Storage.ComposeRequest.of(ImmutableList.of(sourceBlobName1, sourceBlobName2), targetBlob); BlobInfo remoteBlob = storage.compose(req); assertNotNull(remoteBlob); - assertEquals(bucket, remoteBlob.bucket()); - assertEquals(targetBlobName, remoteBlob.name()); + assertEquals(targetBlob.blobId(), remoteBlob.blobId()); byte[] readBytes = storage.readAllBytes(bucket, targetBlobName); byte[] composedBytes = Arrays.copyOf(BLOB_BYTE_CONTENT, BLOB_BYTE_CONTENT.length * 2); System.arraycopy(BLOB_BYTE_CONTENT, 0, composedBytes, BLOB_BYTE_CONTENT.length, @@ -306,8 +301,7 @@ public void testCopyBlobUpdateMetadata() { Storage.CopyRequest req = Storage.CopyRequest.of(bucket, sourceBlobName, targetBlob); BlobInfo remoteBlob = storage.copy(req); assertNotNull(remoteBlob); - assertEquals(bucket, remoteBlob.bucket()); - assertEquals(targetBlobName, remoteBlob.name()); + assertEquals(targetBlob.blobId(), remoteBlob.blobId()); assertEquals(CONTENT_TYPE, remoteBlob.contentType()); assertTrue(storage.delete(bucket, sourceBlobName)); assertTrue(storage.delete(bucket, targetBlobName)); @@ -355,10 +349,8 @@ public void testBatchRequest() { assertEquals(0, updateResponse.gets().size()); BlobInfo remoteUpdatedBlob1 = updateResponse.updates().get(0).get(); BlobInfo remoteUpdatedBlob2 = updateResponse.updates().get(1).get(); - assertEquals(bucket, remoteUpdatedBlob1.bucket()); - assertEquals(bucket, remoteUpdatedBlob2.bucket()); - assertEquals(updatedBlob1.name(), remoteUpdatedBlob1.name()); - assertEquals(updatedBlob2.name(), remoteUpdatedBlob2.name()); + assertEquals(sourceBlob1.blobId(), remoteUpdatedBlob1.blobId()); + assertEquals(sourceBlob2.blobId(), remoteUpdatedBlob2.blobId()); assertEquals(updatedBlob1.contentType(), remoteUpdatedBlob1.contentType()); assertEquals(updatedBlob2.contentType(), remoteUpdatedBlob2.contentType()); @@ -533,8 +525,7 @@ public void testPostSignedUrl() throws IOException { connection.connect(); BlobInfo remoteBlob = storage.get(bucket, blobName); assertNotNull(remoteBlob); - assertEquals(bucket, remoteBlob.bucket()); - assertEquals(blob.name(), remoteBlob.name()); + assertEquals(blob.blobId(), remoteBlob.blobId()); assertTrue(storage.delete(bucket, blobName)); } @@ -546,11 +537,9 @@ public void testGetBlobs() { BlobInfo sourceBlob2 = BlobInfo.builder(bucket, sourceBlobName2).build(); assertNotNull(storage.create(sourceBlob1)); assertNotNull(storage.create(sourceBlob2)); - List remoteInfos = storage.get(sourceBlob1.blobId(), sourceBlob2.blobId()); - assertEquals(sourceBlob1.bucket(), remoteInfos.get(0).bucket()); - assertEquals(sourceBlob1.name(), remoteInfos.get(0).name()); - assertEquals(sourceBlob2.bucket(), remoteInfos.get(1).bucket()); - assertEquals(sourceBlob2.name(), remoteInfos.get(1).name()); + List remoteBlobs = storage.get(sourceBlob1.blobId(), sourceBlob2.blobId()); + assertEquals(sourceBlob1.blobId(), remoteBlobs.get(0).blobId()); + assertEquals(sourceBlob2.blobId(), remoteBlobs.get(1).blobId()); assertTrue(storage.delete(bucket, sourceBlobName1)); assertTrue(storage.delete(bucket, sourceBlobName2)); } @@ -563,8 +552,7 @@ public void testGetBlobsFail() { BlobInfo sourceBlob2 = BlobInfo.builder(bucket, sourceBlobName2).build(); assertNotNull(storage.create(sourceBlob1)); List remoteBlobs = storage.get(sourceBlob1.blobId(), sourceBlob2.blobId()); - assertEquals(sourceBlob1.bucket(), remoteBlobs.get(0).bucket()); - assertEquals(sourceBlob1.name(), remoteBlobs.get(0).name()); + assertEquals(sourceBlob1.blobId(), remoteBlobs.get(0).blobId()); assertNull(remoteBlobs.get(1)); assertTrue(storage.delete(bucket, sourceBlobName1)); } @@ -607,11 +595,9 @@ public void testUpdateBlobs() { List updatedBlobs = storage.update( remoteBlob1.toBuilder().contentType(CONTENT_TYPE).build(), remoteBlob2.toBuilder().contentType(CONTENT_TYPE).build()); - assertEquals(sourceBlob1.bucket(), updatedBlobs.get(0).bucket()); - assertEquals(sourceBlob1.name(), updatedBlobs.get(0).name()); + assertEquals(sourceBlob1.blobId(), updatedBlobs.get(0).blobId()); assertEquals(CONTENT_TYPE, updatedBlobs.get(0).contentType()); - assertEquals(sourceBlob2.bucket(), updatedBlobs.get(1).bucket()); - assertEquals(sourceBlob2.name(), updatedBlobs.get(1).name()); + assertEquals(sourceBlob2.blobId(), updatedBlobs.get(1).blobId()); assertEquals(CONTENT_TYPE, updatedBlobs.get(1).contentType()); assertTrue(storage.delete(bucket, sourceBlobName1)); assertTrue(storage.delete(bucket, sourceBlobName2)); @@ -628,8 +614,7 @@ public void testUpdateBlobsFail() { List updatedBlobs = storage.update( remoteBlob1.toBuilder().contentType(CONTENT_TYPE).build(), sourceBlob2.toBuilder().contentType(CONTENT_TYPE).build()); - assertEquals(sourceBlob1.bucket(), updatedBlobs.get(0).bucket()); - assertEquals(sourceBlob1.name(), updatedBlobs.get(0).name()); + assertEquals(sourceBlob1.blobId(), updatedBlobs.get(0).blobId()); assertEquals(CONTENT_TYPE, updatedBlobs.get(0).contentType()); assertNull(updatedBlobs.get(1)); assertTrue(storage.delete(bucket, sourceBlobName1)); From 87bc1a112eacee8f475f0ebe2734aa35522f4fac Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Fri, 23 Oct 2015 13:30:43 +0200 Subject: [PATCH 5/6] Provide a way to unset values in blob metadata - Use HashMap instead of ImmutableMap in builder.metadata - Use Collections.unmodifiableMap in blobInfo.metadata getter - Convert null values in metadata to Data.nullOf in blobInfo.toPb - Add integration tests - Add javadoc to mark BlobInfo.ImmutableEmptyMap as internal --- .../com/google/gcloud/storage/BlobInfo.java | 19 ++++++-- .../google/gcloud/storage/ITStorageTest.java | 48 +++++++++++++++++-- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java index 841bcc5d3b95..eb490db0f9fb 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java @@ -27,13 +27,14 @@ import com.google.common.base.Function; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import java.io.Serializable; import java.math.BigInteger; import java.util.AbstractMap; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -84,6 +85,9 @@ public StorageObject apply(BlobInfo blobInfo) { private final String contentLanguage; private final Integer componentCount; + /** + * This class is meant for internal use only. Users are discouraged from using this class. + */ public static final class ImmutableEmptyMap extends AbstractMap { @Override @@ -200,7 +204,7 @@ Builder mediaLink(String mediaLink) { public Builder metadata(Map metadata) { this.metadata = metadata != null ? - ImmutableMap.copyOf(metadata) : Data.nullOf(ImmutableEmptyMap.class); + new HashMap(metadata) : Data.nullOf(ImmutableEmptyMap.class); return this; } @@ -327,7 +331,7 @@ public String mediaLink() { } public Map metadata() { - return Data.isNull(metadata) ? null : metadata; + return metadata == null || Data.isNull(metadata) ? null : Collections.unmodifiableMap(metadata); } public Long generation() { @@ -414,6 +418,14 @@ public ObjectAccessControl apply(Acl acl) { if (owner != null) { storageObject.setOwner(new Owner().setEntity(owner.toPb())); } + Map pbMetadata = metadata; + if (metadata != null && !Data.isNull(metadata)) { + pbMetadata = new HashMap<>(); + for (String key : metadata.keySet()) { + pbMetadata.put(key, firstNonNull(metadata.get(key), Data.nullOf(String.class))); + } + } + storageObject.setMetadata(pbMetadata); storageObject.setCacheControl(cacheControl); storageObject.setContentEncoding(contentEncoding); storageObject.setCrc32c(crc32c); @@ -421,7 +433,6 @@ public ObjectAccessControl apply(Acl acl) { storageObject.setGeneration(generation); storageObject.setMd5Hash(md5); storageObject.setMediaLink(mediaLink); - storageObject.setMetadata(metadata); storageObject.setMetageneration(metageneration); storageObject.setContentDisposition(contentDisposition); storageObject.setComponentCount(componentCount); diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java index 20fdc84ff8a7..3acb09a18080 100644 --- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java +++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java @@ -37,8 +37,10 @@ import java.net.URLConnection; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -174,17 +176,57 @@ public void testUpdateBlob() { @Test public void testUpdateBlobReplaceMetadata() { String blobName = "test-update-blob-replace-metadata"; + ImmutableMap metadata = ImmutableMap.of("k1", "a"); + ImmutableMap newMetadata = ImmutableMap.of("k2", "b"); BlobInfo blob = BlobInfo.builder(bucket, blobName) .contentType(CONTENT_TYPE) - .metadata(ImmutableMap.of("k1", "a")) + .metadata(metadata) .build(); assertNotNull(storage.create(blob)); BlobInfo updatedBlob = storage.update(blob.toBuilder().metadata(null).build()); assertNotNull(updatedBlob); assertNull(updatedBlob.metadata()); - updatedBlob = storage.update(blob.toBuilder().metadata(ImmutableMap.of("k2", "b")).build()); + updatedBlob = storage.update(blob.toBuilder().metadata(newMetadata).build()); assertEquals(blob.blobId(), updatedBlob.blobId()); - assertEquals(ImmutableMap.of("k2", "b"), updatedBlob.metadata()); + assertEquals(newMetadata, updatedBlob.metadata()); + assertTrue(storage.delete(bucket, blobName)); + } + + @Test + public void testUpdateBlobMergeMetadata() { + String blobName = "test-update-blob-merge-metadata"; + ImmutableMap metadata = ImmutableMap.of("k1", "a"); + ImmutableMap newMetadata = ImmutableMap.of("k2", "b"); + ImmutableMap expectedMetadata = ImmutableMap.of("k1", "a", "k2", "b"); + BlobInfo blob = BlobInfo.builder(bucket, blobName) + .contentType(CONTENT_TYPE) + .metadata(metadata) + .build(); + assertNotNull(storage.create(blob)); + BlobInfo updatedBlob = storage.update(blob.toBuilder().metadata(newMetadata).build()); + assertNotNull(updatedBlob); + assertEquals(blob.blobId(), updatedBlob.blobId()); + assertEquals(expectedMetadata, updatedBlob.metadata()); + assertTrue(storage.delete(bucket, blobName)); + } + + @Test + public void testUpdateBlobUnsetMetadata() { + String blobName = "test-update-blob-unset-metadata"; + ImmutableMap metadata = ImmutableMap.of("k1", "a", "k2", "b"); + Map newMetadata = new HashMap<>(); + newMetadata.put("k1", "a"); + newMetadata.put("k2", null); + ImmutableMap expectedMetadata = ImmutableMap.of("k1", "a"); + BlobInfo blob = BlobInfo.builder(bucket, blobName) + .contentType(CONTENT_TYPE) + .metadata(metadata) + .build(); + assertNotNull(storage.create(blob)); + BlobInfo updatedBlob = storage.update(blob.toBuilder().metadata(newMetadata).build()); + assertNotNull(updatedBlob); + assertEquals(blob.blobId(), updatedBlob.blobId()); + assertEquals(expectedMetadata, updatedBlob.metadata()); assertTrue(storage.delete(bucket, blobName)); } From 682848a60389769e66c7a0e4e6f3ec4ac7dad572 Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Fri, 23 Oct 2015 19:50:53 +0200 Subject: [PATCH 6/6] Use newHashMapWithExpectedSize instead of new HashMap --- .../src/main/java/com/google/gcloud/storage/BlobInfo.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java index eb490db0f9fb..ec3ef36708cb 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BlobInfo.java @@ -29,6 +29,7 @@ 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 java.io.Serializable; import java.math.BigInteger; @@ -420,7 +421,7 @@ public ObjectAccessControl apply(Acl acl) { } Map pbMetadata = metadata; if (metadata != null && !Data.isNull(metadata)) { - pbMetadata = new HashMap<>(); + pbMetadata = Maps.newHashMapWithExpectedSize(metadata.size()); for (String key : metadata.keySet()) { pbMetadata.put(key, firstNonNull(metadata.get(key), Data.nullOf(String.class))); }