diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/spi/StorageRpc.java b/gcloud-java-storage/src/main/java/com/google/gcloud/spi/StorageRpc.java index b82acae9cd39..b7ac99bf909e 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/spi/StorageRpc.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/spi/StorageRpc.java @@ -16,6 +16,8 @@ package com.google.gcloud.spi; +import static com.google.common.base.MoreObjects.firstNonNull; + import com.google.api.services.storage.model.Bucket; import com.google.api.services.storage.model.StorageObject; import com.google.common.collect.ImmutableList; @@ -106,9 +108,12 @@ class BatchRequest { public BatchRequest(Iterable>> toDelete, Iterable>> toUpdate, Iterable>> toGet) { - this.toDelete = ImmutableList.copyOf(toDelete); - this.toUpdate = ImmutableList.copyOf(toUpdate); - this.toGet = ImmutableList.copyOf(toGet); + this.toDelete = ImmutableList.copyOf( + firstNonNull(toDelete, ImmutableList.>>of())); + this.toUpdate = ImmutableList.copyOf( + firstNonNull(toUpdate, ImmutableList.>>of())); + this.toGet = ImmutableList.copyOf( + firstNonNull(toGet, ImmutableList.>>of())); } } 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 e0863d23a70c..189ae2ad3065 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 @@ -20,12 +20,17 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.gcloud.storage.Blob.BlobSourceOption.convert; +import com.google.common.base.Function; +import com.google.common.collect.Lists; import com.google.gcloud.spi.StorageRpc; import com.google.gcloud.storage.Storage.BlobTargetOption; import com.google.gcloud.storage.Storage.CopyRequest; import com.google.gcloud.storage.Storage.SignUrlOption; import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -256,4 +261,74 @@ public URL signUrl(long expirationTimeInSeconds, SignUrlOption... options) { public Storage storage() { return 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. + * + * @param storage the storage service used to issue the request + * @param infos the blobs to get + * @return an immutable list of {@code Blob} objects. If a blob does not exist or access to it has + * been denied the corresponding item in the list is {@code null}. + * @throws StorageException upon failure + */ + public static List get(final Storage storage, BlobInfo... infos) { + checkNotNull(storage); + checkNotNull(infos); + if (infos.length == 0) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(Lists.transform(storage.get(infos), + new Function() { + @Override + public Blob apply(BlobInfo f) { + return f != null ? new Blob(storage, f) : null; + } + })); + } + + /** + * 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. + * + * @param storage the storage service used to issue the request + * @param infos the blobs to update + * @return an immutable list of {@code Blob} objects. If a blob does not exist or access to it has + * been denied the corresponding item in the list is {@code null}. + * @throws StorageException upon failure + */ + public static List update(final Storage storage, BlobInfo... infos) { + checkNotNull(storage); + checkNotNull(infos); + if (infos.length == 0) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(Lists.transform(storage.update(infos), + new Function() { + @Override + public Blob apply(BlobInfo f) { + return f != null ? new Blob(storage, f) : null; + } + })); + } + + /** + * 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. + * + * @param storage the storage service used to issue the request + * @param infos the blobs to delete + * @return an immutable list of booleans. If a blob has been deleted the corresponding item in the + * list is {@code true}. If deletion failed or access to the resource was denied the item is + * {@code false}. + * @throws StorageException upon failure + */ + public static List delete(Storage storage, BlobInfo... infos) { + checkNotNull(storage); + checkNotNull(infos); + if (infos.length == 0) { + return Collections.emptyList(); + } + return storage.delete(infos); + } } diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Bucket.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Bucket.java index d7cac447d051..95cc5f262e03 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Bucket.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Bucket.java @@ -25,6 +25,7 @@ import com.google.gcloud.storage.Storage.BucketTargetOption; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -146,13 +147,18 @@ public Blob get(String blob, BlobSourceOption... options) { /** * Returns a list of requested blobs in this bucket. Blobs that do not exist are null. * - * @param blobNames names of the requested blobs + * @param blobName1 first blob to get + * @param blobName2 second blob to get + * @param blobNames other blobs to get + * @return an immutable list of {@code Blob} objects. * @throws StorageException upon failure */ - public List getAll(String... blobNames) { + public List get(String blobName1, String blobName2, String... blobNames) { BatchRequest.Builder batch = BatchRequest.builder(); - for (String blobName : blobNames) { - batch.get(info.name(), blobName); + batch.get(info.name(), blobName1); + batch.get(info.name(), blobName2); + for (String name : blobNames) { + batch.get(info.name(), name); } List blobs = new ArrayList<>(blobNames.length); BatchResponse response = storage.apply(batch.build()); @@ -160,7 +166,7 @@ public List getAll(String... blobNames) { BlobInfo blobInfo = result.get(); blobs.add(blobInfo != null ? new Blob(storage, blobInfo) : null); } - return blobs; + return Collections.unmodifiableList(blobs); } /** 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 2ce25a8190f3..a980f0ed3fbf 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 @@ -563,6 +563,14 @@ public static Builder builder() { */ BlobInfo update(BlobInfo blobInfo, BlobTargetOption... options); + /** + * Update blob information. + * + * @return the updated blob + * @throws StorageException upon failure + */ + BlobInfo update(BlobInfo blobInfo); + /** * Delete the requested bucket. * @@ -638,4 +646,35 @@ public static Builder builder() { * @see Signed-URLs */ URL signUrl(BlobInfo blobInfo, long expirationTimeInSeconds, SignUrlOption... options); + + /** + * Gets the requested blobs. A batch request is used to perform this call. + * + * @param blobInfos blobs to get + * @return an immutable list of {@code BlobInfo} objects. If a blob does not exist or access to it + * has been denied the corresponding item in the list is {@code null}. + * @throws StorageException upon failure + */ + List get(BlobInfo... blobInfos); + + /** + * Updates the requested blobs. A batch request is used to perform this call. + * + * @param blobInfos blobs to update + * @return an immutable list of {@code BlobInfo} objects. If a blob does not exist or access to it + * has been denied the corresponding item in the list is {@code null}. + * @throws StorageException upon failure + */ + List update(BlobInfo... blobInfos); + + /** + * Deletes the requested blobs. A batch request is used to perform this call. + * + * @param blobInfos blobs to delete + * @return an immutable list of booleans. If a blob has been deleted the corresponding item in the + * list is {@code true}. If deletion failed or access to the resource was denied the item is + * {@code false}. + * @throws StorageException upon failure + */ + List delete(BlobInfo... blobInfos); } diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java index 0dd4a428a02d..6e32220746ca 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java @@ -62,6 +62,7 @@ import java.security.Signature; import java.security.SignatureException; import java.util.Arrays; +import java.util.Collections; import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -348,6 +349,11 @@ public StorageObject call() { } } + @Override + public BlobInfo update(BlobInfo blobInfo) { + return update(blobInfo, new BlobTargetOption[0]); + } + @Override public boolean delete(String bucket, BucketSourceOption... options) { final com.google.api.services.storage.model.Bucket bucketPb = BucketInfo.of(bucket).toPb(); @@ -577,6 +583,46 @@ public URL signUrl(BlobInfo blobInfo, long expiration, SignUrlOption... options) } } + @Override + public List get(BlobInfo... blobInfos) { + BatchRequest.Builder requestBuilder = BatchRequest.builder(); + for (BlobInfo blobInfo : blobInfos) { + requestBuilder.get(blobInfo.bucket(), blobInfo.name()); + } + BatchResponse response = apply(requestBuilder.build()); + return Collections.unmodifiableList(transformResultList(response.gets(), null)); + } + + @Override + public List update(BlobInfo... blobInfos) { + BatchRequest.Builder requestBuilder = BatchRequest.builder(); + for (BlobInfo blobInfo : blobInfos) { + requestBuilder.update(blobInfo); + } + BatchResponse response = apply(requestBuilder.build()); + return Collections.unmodifiableList(transformResultList(response.updates(), null)); + } + + @Override + public List delete(BlobInfo... blobInfos) { + BatchRequest.Builder requestBuilder = BatchRequest.builder(); + for (BlobInfo blobInfo : blobInfos) { + requestBuilder.delete(blobInfo.bucket(), blobInfo.name()); + } + BatchResponse response = apply(requestBuilder.build()); + return Collections.unmodifiableList(transformResultList(response.deletes(), Boolean.FALSE)); + } + + private static List transformResultList( + List> results, final T errorValue) { + return Lists.transform(results, new Function, T>() { + @Override + public T apply(BatchResponse.Result f) { + return f.failed() ? errorValue : f.get(); + } + }); + } + private Map optionMap(Long generation, Long metaGeneration, Iterable options) { return optionMap(generation, metaGeneration, options, false); diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobTest.java index 4923b8b772e8..ece24eeacd1e 100644 --- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobTest.java +++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobTest.java @@ -25,19 +25,25 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import com.google.api.client.util.Lists; import com.google.gcloud.storage.Storage.CopyRequest; import org.easymock.Capture; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.net.URL; +import java.util.Arrays; +import java.util.List; public class BlobTest { private static final BlobInfo BLOB_INFO = BlobInfo.of("b", "n"); + private static final BlobInfo[] BLOB_INFO_ARRAY = {BlobInfo.of("b1", "n1"), + BlobInfo.of("b2", "n2"), BlobInfo.of("b3", "n3")}; private Storage storage; private Blob blob; @@ -94,7 +100,7 @@ public void testReload() throws Exception { @Test public void testUpdate() throws Exception { BlobInfo updatedInfo = BLOB_INFO.toBuilder().cacheControl("c").build(); - expect(storage.update(updatedInfo)).andReturn(updatedInfo); + expect(storage.update(updatedInfo, new Storage.BlobTargetOption[0])).andReturn(updatedInfo); replay(storage); Blob updatedBlob = blob.update(updatedInfo); assertSame(storage, blob.storage()); @@ -159,4 +165,95 @@ public void testSignUrl() throws Exception { replay(storage); assertEquals(url, blob.signUrl(100)); } + + @Test + public void testGetNone() throws Exception { + replay(storage); + assertTrue(Blob.get(storage).isEmpty()); + } + + @Test + public void testGetSome() throws Exception { + List blobInfoList = Arrays.asList(BLOB_INFO_ARRAY); + expect(storage.get(BLOB_INFO_ARRAY)).andReturn(blobInfoList); + replay(storage); + List result = Blob.get(storage, BLOB_INFO_ARRAY); + assertEquals(blobInfoList.size(), result.size()); + for (int i = 0; i < blobInfoList.size(); i++) { + assertEquals(blobInfoList.get(i), result.get(i).info()); + } + } + + @Test + public void testGetSomeNull() throws Exception { + List blobInfoList = Arrays.asList(BLOB_INFO_ARRAY[0], null, BLOB_INFO_ARRAY[2]); + expect(storage.get(BLOB_INFO_ARRAY)).andReturn(blobInfoList); + replay(storage); + List result = Blob.get(storage, BLOB_INFO_ARRAY); + assertEquals(blobInfoList.size(), result.size()); + for (int i = 0; i < blobInfoList.size(); i++) { + if (blobInfoList.get(i) != null) { + assertEquals(blobInfoList.get(i), result.get(i).info()); + } else { + assertNull(result.get(i)); + } + } + } + + @Test + public void testUpdateNone() throws Exception { + replay(storage); + assertTrue(Blob.update(storage).isEmpty()); + } + + @Test + public void testUpdateSome() throws Exception { + List blobInfoList = Lists.newArrayListWithCapacity(BLOB_INFO_ARRAY.length); + for (BlobInfo info : BLOB_INFO_ARRAY) { + blobInfoList.add(info.toBuilder().contentType("content").build()); + } + expect(storage.update(BLOB_INFO_ARRAY)).andReturn(blobInfoList); + replay(storage); + List result = Blob.update(storage, BLOB_INFO_ARRAY); + assertEquals(blobInfoList.size(), result.size()); + for (int i = 0; i < blobInfoList.size(); i++) { + assertEquals(blobInfoList.get(i), result.get(i).info()); + } + } + + @Test + public void testUpdateSomeNull() throws Exception { + List blobInfoList = Arrays.asList( + BLOB_INFO_ARRAY[0].toBuilder().contentType("content").build(), null, + BLOB_INFO_ARRAY[2].toBuilder().contentType("content").build()); + expect(storage.update(BLOB_INFO_ARRAY)).andReturn(blobInfoList); + replay(storage); + List result = Blob.update(storage, BLOB_INFO_ARRAY); + assertEquals(blobInfoList.size(), result.size()); + for (int i = 0; i < blobInfoList.size(); i++) { + if (blobInfoList.get(i) != null) { + assertEquals(blobInfoList.get(i), result.get(i).info()); + } else { + assertNull(result.get(i)); + } + } + } + + @Test + public void testDeleteNone() throws Exception { + replay(storage); + assertTrue(Blob.delete(storage).isEmpty()); + } + + @Test + public void testDeleteSome() throws Exception { + List deleleResultList = Arrays.asList(true, true, true); + expect(storage.delete(BLOB_INFO_ARRAY)).andReturn(deleleResultList); + replay(storage); + List result = Blob.delete(storage, BLOB_INFO_ARRAY); + assertEquals(deleleResultList.size(), result.size()); + for (int i = 0; i < deleleResultList.size(); i++) { + assertEquals(deleleResultList.get(i), result.get(i)); + } + } } diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketTest.java index 9b8546ecc27d..5fddc23691d6 100644 --- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketTest.java +++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketTest.java @@ -143,7 +143,7 @@ public void testGetAll() throws Exception { new BatchResponse(Collections.EMPTY_LIST, Collections.EMPTY_LIST, batchResultList); expect(storage.apply(capture(capturedBatchRequest))).andReturn(response); replay(storage); - List blobs = bucket.getAll("n1", "n2", "n3"); + List blobs = bucket.get("n1", "n2", "n3"); Set blobInfoSet = capturedBatchRequest.getValue().toGet().keySet(); assertEquals(batchResultList.size(), blobInfoSet.size()); for (BlobInfo info : BLOB_INFO_RESULTS) { 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 4c3ff1fa9a7c..2ca36c8ec1e3 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 @@ -20,6 +20,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -36,6 +37,7 @@ import java.util.Arrays; import java.util.Calendar; import java.util.Iterator; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -464,4 +466,101 @@ public void testPostSignedUrl() throws IOException { assertEquals(blob.name(), remoteBlob.name()); assertTrue(storage.delete(bucket, blobName)); } + + @Test + public void testGetBlobs() { + String sourceBlobName1 = "test-get-blobs-1"; + String sourceBlobName2 = "test-get-blobs-2"; + BlobInfo sourceBlob1 = BlobInfo.of(bucket, sourceBlobName1); + BlobInfo sourceBlob2 = BlobInfo.of(bucket, sourceBlobName2); + assertNotNull(storage.create(sourceBlob1)); + assertNotNull(storage.create(sourceBlob2)); + List remoteInfos = storage.get(sourceBlob1, sourceBlob2); + 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()); + assertTrue(storage.delete(bucket, sourceBlobName1)); + assertTrue(storage.delete(bucket, sourceBlobName2)); + } + + @Test + public void testGetBlobsFail() { + String sourceBlobName1 = "test-get-blobs-fail-1"; + String sourceBlobName2 = "test-get-blobs-fail-2"; + BlobInfo sourceBlob1 = BlobInfo.of(bucket, sourceBlobName1); + BlobInfo sourceBlob2 = BlobInfo.of(bucket, sourceBlobName2); + assertNotNull(storage.create(sourceBlob1)); + List remoteBlobs = storage.get(sourceBlob1, sourceBlob2); + assertEquals(sourceBlob1.bucket(), remoteBlobs.get(0).bucket()); + assertEquals(sourceBlob1.name(), remoteBlobs.get(0).name()); + assertNull(remoteBlobs.get(1)); + assertTrue(storage.delete(bucket, sourceBlobName1)); + } + + @Test + public void testDeleteBlobs() { + String sourceBlobName1 = "test-delete-blobs-1"; + String sourceBlobName2 = "test-delete-blobs-2"; + BlobInfo sourceBlob1 = BlobInfo.of(bucket, sourceBlobName1); + BlobInfo sourceBlob2 = BlobInfo.of(bucket, sourceBlobName2); + assertNotNull(storage.create(sourceBlob1)); + assertNotNull(storage.create(sourceBlob2)); + List deleteStatus = storage.delete(sourceBlob1, sourceBlob2); + assertTrue(deleteStatus.get(0)); + assertTrue(deleteStatus.get(1)); + } + + @Test + public void testDeleteBlobsFail() { + String sourceBlobName1 = "test-delete-blobs-fail-1"; + String sourceBlobName2 = "test-delete-blobs-fail-2"; + BlobInfo sourceBlob1 = BlobInfo.of(bucket, sourceBlobName1); + BlobInfo sourceBlob2 = BlobInfo.of(bucket, sourceBlobName2); + assertNotNull(storage.create(sourceBlob1)); + List deleteStatus = storage.delete(sourceBlob1, sourceBlob2); + assertTrue(deleteStatus.get(0)); + assertTrue(!deleteStatus.get(1)); + } + + @Test + public void testUpdateBlobs() { + String sourceBlobName1 = "test-update-blobs-1"; + String sourceBlobName2 = "test-update-blobs-2"; + BlobInfo sourceBlob1 = BlobInfo.of(bucket, sourceBlobName1); + BlobInfo sourceBlob2 = BlobInfo.of(bucket, sourceBlobName2); + BlobInfo remoteBlob1 = storage.create(sourceBlob1); + BlobInfo remoteBlob2 = storage.create(sourceBlob2); + assertNotNull(remoteBlob1); + assertNotNull(remoteBlob2); + 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(CONTENT_TYPE, updatedBlobs.get(0).contentType()); + assertEquals(sourceBlob2.bucket(), updatedBlobs.get(1).bucket()); + assertEquals(sourceBlob2.name(), updatedBlobs.get(1).name()); + assertEquals(CONTENT_TYPE, updatedBlobs.get(1).contentType()); + assertTrue(storage.delete(bucket, sourceBlobName1)); + assertTrue(storage.delete(bucket, sourceBlobName2)); + } + + @Test + public void testUpdateBlobsFail() { + String sourceBlobName1 = "test-update-blobs-fail-1"; + String sourceBlobName2 = "test-update-blobs-fail-2"; + BlobInfo sourceBlob1 = BlobInfo.of(bucket, sourceBlobName1); + BlobInfo sourceBlob2 = BlobInfo.of(bucket, sourceBlobName2); + BlobInfo remoteBlob1 = storage.create(sourceBlob1); + assertNotNull(remoteBlob1); + 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(CONTENT_TYPE, updatedBlobs.get(0).contentType()); + assertNull(updatedBlobs.get(1)); + assertTrue(storage.delete(bucket, sourceBlobName1)); + } } diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java index 8c55f2cc4e44..fa0daa976eb0 100644 --- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java +++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java @@ -857,6 +857,135 @@ public void testSignUrlWithOptions() throws NoSuchAlgorithmException, InvalidKey EasyMock.verify(credentialsMock); } + @Test + public void testGetAll() { + BlobInfo blobInfo1 = BlobInfo.of(BUCKET_NAME1, BLOB_NAME1); + BlobInfo blobInfo2 = BlobInfo.of(BUCKET_NAME1, BLOB_NAME2); + StorageObject storageObject1 = blobInfo1.toPb(); + StorageObject storageObject2 = blobInfo2.toPb(); + List toGet = ImmutableList.of(storageObject1, storageObject2); + + Map> deleteResult = ImmutableMap.of(); + Map> updateResult = ImmutableMap.of(); + Map> getResult = Maps.toMap(toGet, + new Function>() { + @Override + public Tuple apply(StorageObject f) { + return Tuple.of(f, null); + } + }); + StorageRpc.BatchResponse res = + new StorageRpc.BatchResponse(deleteResult, updateResult, getResult); + + EasyMock.expect(optionsMock.storageRpc()).andReturn(storageRpcMock); + Capture capturedBatchRequest = Capture.newInstance(); + EasyMock.expect(storageRpcMock.batch(EasyMock.capture(capturedBatchRequest))).andReturn(res); + EasyMock.replay(optionsMock, storageRpcMock); + storage = StorageFactory.instance().get(optionsMock); + List resultBlobs = storage.get(blobInfo1, blobInfo2); + + // Verify captured StorageRpc.BatchRequest + List>> capturedToGet = + capturedBatchRequest.getValue().toGet; + assertTrue(capturedBatchRequest.getValue().toDelete.isEmpty()); + assertTrue(capturedBatchRequest.getValue().toUpdate.isEmpty()); + for (int i = 0; i < capturedToGet.size(); i++) { + assertEquals(toGet.get(i), capturedToGet.get(i).x()); + assertTrue(capturedToGet.get(i).y().isEmpty()); + } + + // Verify result + for (int i = 0; i < resultBlobs.size(); i++) { + assertEquals(toGet.get(i), resultBlobs.get(i).toPb()); + } + } + + @Test + public void testUpdateAll() { + BlobInfo blobInfo1 = BlobInfo.builder(BUCKET_NAME1, BLOB_NAME1).contentType("type").build(); + BlobInfo blobInfo2 = BlobInfo.builder(BUCKET_NAME1, BLOB_NAME2).contentType("type").build(); + StorageObject storageObject1 = blobInfo1.toPb(); + StorageObject storageObject2 = blobInfo2.toPb(); + List toUpdate = ImmutableList.of(storageObject1, storageObject2); + + Map> deleteResult = ImmutableMap.of(); + Map> getResult = ImmutableMap.of(); + Map> updateResult = Maps.toMap(toUpdate, + new Function>() { + @Override + public Tuple apply(StorageObject f) { + return Tuple.of(f, null); + } + }); + StorageRpc.BatchResponse res = + new StorageRpc.BatchResponse(deleteResult, updateResult, getResult); + + EasyMock.expect(optionsMock.storageRpc()).andReturn(storageRpcMock); + Capture capturedBatchRequest = Capture.newInstance(); + EasyMock.expect(storageRpcMock.batch(EasyMock.capture(capturedBatchRequest))).andReturn(res); + EasyMock.replay(optionsMock, storageRpcMock); + storage = StorageFactory.instance().get(optionsMock); + List resultBlobs = storage.update(blobInfo1, blobInfo2); + + // Verify captured StorageRpc.BatchRequest + List>> capturedToUpdate = + capturedBatchRequest.getValue().toUpdate; + assertTrue(capturedBatchRequest.getValue().toDelete.isEmpty()); + assertTrue(capturedBatchRequest.getValue().toGet.isEmpty()); + for (int i = 0; i < capturedToUpdate.size(); i++) { + assertEquals(toUpdate.get(i), capturedToUpdate.get(i).x()); + assertTrue(capturedToUpdate.get(i).y().isEmpty()); + } + + // Verify result + for (int i = 0; i < resultBlobs.size(); i++) { + assertEquals(toUpdate.get(i), resultBlobs.get(i).toPb()); + } + } + + @Test + public void testDeleteAll() { + BlobInfo blobInfo1 = BlobInfo.builder(BUCKET_NAME1, BLOB_NAME1).build(); + BlobInfo blobInfo2 = BlobInfo.builder(BUCKET_NAME1, BLOB_NAME2).build(); + StorageObject storageObject1 = blobInfo1.toPb(); + StorageObject storageObject2 = blobInfo2.toPb(); + List toUpdate = ImmutableList.of(storageObject1, storageObject2); + + Map> updateResult = ImmutableMap.of(); + Map> getResult = ImmutableMap.of(); + Map> deleteResult = Maps.toMap(toUpdate, + new Function>() { + @Override + public Tuple apply(StorageObject f) { + return Tuple.of(true, null); + } + }); + StorageRpc.BatchResponse res = + new StorageRpc.BatchResponse(deleteResult, updateResult, getResult); + + EasyMock.expect(optionsMock.storageRpc()).andReturn(storageRpcMock); + Capture capturedBatchRequest = Capture.newInstance(); + EasyMock.expect(storageRpcMock.batch(EasyMock.capture(capturedBatchRequest))).andReturn(res); + EasyMock.replay(optionsMock, storageRpcMock); + storage = StorageFactory.instance().get(optionsMock); + List deleteResults = storage.delete(blobInfo1, blobInfo2); + + // Verify captured StorageRpc.BatchRequest + List>> capturedToDelete = + capturedBatchRequest.getValue().toDelete; + assertTrue(capturedBatchRequest.getValue().toUpdate.isEmpty()); + assertTrue(capturedBatchRequest.getValue().toGet.isEmpty()); + for (int i = 0; i < capturedToDelete.size(); i++) { + assertEquals(toUpdate.get(i), capturedToDelete.get(i).x()); + assertTrue(capturedToDelete.get(i).y().isEmpty()); + } + + // Verify result + for (Boolean deleteStatus : deleteResults) { + assertTrue(deleteStatus); + } + } + @Test public void testRetryableException() { BlobInfo blob = BlobInfo.of(BUCKET_NAME1, BLOB_NAME1);