From 44cefb06a9f1a2393f0191fe93e34b9776ced00d Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Tue, 22 Sep 2015 17:00:03 +0200 Subject: [PATCH] Blob and Bucket extended with more functionalities, added tests --- .../java/com/google/gcloud/storage/Blob.java | 73 ++++++-- .../com/google/gcloud/storage/Bucket.java | 103 +++++++++-- .../com/google/gcloud/storage/BlobTest.java | 5 +- .../com/google/gcloud/storage/BucketTest.java | 160 ++++++++++++++++++ 4 files changed, 313 insertions(+), 28 deletions(-) create mode 100644 gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketTest.java 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 dd83ee84182a..d52b15eae8c2 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 @@ -1,4 +1,3 @@ - /* * Copyright 2015 Google Inc. All Rights Reserved. * @@ -31,7 +30,6 @@ import java.net.URL; import java.util.Objects; - /** * A Google cloud storage object. */ @@ -89,18 +87,44 @@ static Storage.BlobSourceOption[] convert(BlobInfo blobInfo, BlobSourceOption... } } + /** + * Construct a {@code Blob} object for the provided {@code BlobInfo}. The storage service is used + * to issue requests. + * + * @param storage the storage service used for issuing requests + * @param info blob's info + */ public Blob(Storage storage, BlobInfo info) { this.storage = checkNotNull(storage); this.info = checkNotNull(info); } + /** + * Construct a {@code Blob} object for the provided bucket and blob names. The storage service is + * used to issue requests. + * + * @param storage the storage service used for issuing requests + * @param bucket bucket's name + * @param blob blob's name + */ + public Blob(Storage storage, String bucket, String blob) { + this.storage = checkNotNull(storage); + this.info = BlobInfo.of(bucket, blob); + } + + /** + * Get the blobs's information. + * + * @return a {@code BlobInfo} object for this blob + */ public BlobInfo info() { return info; } /** - * Returns true if this blob exists. + * Check if this blob exists. * + * @return true if this blob exists, false otherwise * @throws StorageException upon failure */ public boolean exists() { @@ -108,8 +132,10 @@ public boolean exists() { } /** - * Returns the blob's content. + * Return this blob's content. * + * @param options blob read options + * @return the blob's content * @throws StorageException upon failure */ public byte[] content(Storage.BlobSourceOption... options) { @@ -117,10 +143,12 @@ public byte[] content(Storage.BlobSourceOption... options) { } /** - * Updates the blob's information. Bucket or blob's name cannot be changed by this method. If you + * Update the blob's information. Bucket or blob's name cannot be changed by this method. If you * want to rename the blob or move it to a different bucket use the {@link #copyTo} and * {@link #delete} operations. * + * @param blobInfo new blob's information. Bucket and blob names must match the current ones + * @param options update options * @throws StorageException upon failure */ public void update(BlobInfo blobInfo, BlobTargetOption... options) { @@ -130,9 +158,10 @@ public void update(BlobInfo blobInfo, BlobTargetOption... options) { } /** - * Deletes this blob. + * Delete this blob. * - * @return true if bucket was deleted + * @param options blob delete options + * @return true if blob was deleted * @throws StorageException upon failure */ public boolean delete(BlobSourceOption... options) { @@ -140,9 +169,11 @@ public boolean delete(BlobSourceOption... options) { } /** - * Send a copy request. + * Copy this blob. * - * @return the copied blob. + * @param target target blob + * @param options source blob options + * @return the copied blob * @throws StorageException upon failure */ public Blob copyTo(BlobInfo target, BlobSourceOption... options) { @@ -150,9 +181,12 @@ public Blob copyTo(BlobInfo target, BlobSourceOption... options) { } /** - * Send a copy request. + * Copy this blob. * - * @return the copied blob. + * @param target target blob + * @param sourceOptions source blob options + * @param targetOptions target blob options + * @return the copied blob * @throws StorageException upon failure */ public Blob copyTo(BlobInfo target, Iterable sourceOptions, @@ -165,8 +199,10 @@ public Blob copyTo(BlobInfo target, Iterable sourceOptions, } /** - * Returns a channel for reading this blob's content. + * Return a channel for reading this blob's content. * + * @param options blob read options + * @return a {@code BlobReadChannel} object to read this blob * @throws StorageException upon failure */ public BlobReadChannel reader(BlobSourceOption... options) { @@ -174,8 +210,10 @@ public BlobReadChannel reader(BlobSourceOption... options) { } /** - * Returns a channel for writing to this blob. + * Return a channel for writing to this blob. * + * @param options target blob options + * @return a {@code BlobWriteChannel} object for writing to this blob * @throws StorageException upon failure */ public BlobWriteChannel writer(BlobTargetOption... options) { @@ -183,18 +221,25 @@ public BlobWriteChannel writer(BlobTargetOption... options) { } /** - * Generates a signed URL for this blob. If you want to allow access to for a fixed amount of time + * Generate a signed URL for this blob. If you want to allow access to for a fixed amount of time * for this blob, you can use this method to generate a URL that is only valid within a certain * time period. This is particularly useful if you don't want publicly accessible blobs, but don't * want to require users to explicitly log in. * * @param expirationTimeInSeconds the signed URL expiration (using epoch time) + * @param options signed url options + * @return a signed URL for this bucket and the specified options * @see Signed-URLs */ public URL signUrl(long expirationTimeInSeconds, SignUrlOption... options) { return storage.signUrl(info, expirationTimeInSeconds, options); } + /** + * Get this blobs's {@code Storage} object. + * + * @return the storage service used by this blob to issue requests + */ public Storage storage() { return storage; } 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 fd93a5bda6ea..56d27c3c621f 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 @@ -20,8 +20,10 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.gcloud.storage.Storage.BlobSourceOption; +import com.google.gcloud.storage.Storage.BlobTargetOption; import com.google.gcloud.storage.Storage.BucketSourceOption; import com.google.gcloud.storage.Storage.BucketTargetOption; +import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -34,14 +36,43 @@ public final class Bucket { private final Storage storage; private BucketInfo info; + /** + * Construct a {@code Bucket} object for the provided {@code BucketInfo}. The storage service is + * used to issue requests. + * + * @param storage the storage service used for issuing requests + * @param info bucket's info + */ public Bucket(Storage storage, BucketInfo info) { this.storage = checkNotNull(storage); this.info = checkNotNull(info); } /** - * Returns true if this bucket exists. + * Construct a {@code Bucket} object for the provided name. The storage service is used to issue + * requests. + * + * @param storage the storage service used for issuing requests + * @param bucket bucket's name + */ + public Bucket(Storage storage, String bucket) { + this.storage = checkNotNull(storage); + this.info = BucketInfo.of(bucket); + } + + /** + * Get the bucket's information. + * + * @return a {@code BucketInfo} object for this bucket + */ + public BucketInfo info() { + return info; + } + + /** + * Check if this bucket exists. * + * @return true if this bucket exists, false otherwise * @throws StorageException upon failure */ public boolean exists() { @@ -51,6 +82,8 @@ public boolean exists() { /** * Update the bucket's information. Bucket's name cannot be changed. * + * @param bucketInfo new bucket's information. Name must match the one of the current bucket + * @param options update options * @throws StorageException upon failure */ public void update(BucketInfo bucketInfo, BucketTargetOption... options) { @@ -61,6 +94,7 @@ public void update(BucketInfo bucketInfo, BucketTargetOption... options) { /** * Delete this bucket. * + * @param options bucket delete options * @return true if bucket was deleted * @throws StorageException upon failure */ @@ -68,26 +102,69 @@ public boolean delete(BucketSourceOption... options) { return storage.delete(info.name(), options); } - public ListResult list(Storage.BlobListOption... options) { - return storage.list(info.name(), options); + /** + * List blobs in this bucket. + * + * @param options options for listing blobs + * @return the paginated list of {@code Blob} objects in this bucket + * @throws StorageException upon failure + */ + public ListResult list(Storage.BlobListOption... options) { + return new BlobListResult(storage, storage.list(info.name(), options)); } - public BlobInfo get(String blob, BlobSourceOption... options) { - return null; + /** + * Return the requested blob in this bucket or {@code null} if not found. + * + * @param blob name of the requested blob + * @param options blob search options + * @return the requested blob in this bucket or {@code null} if not found + * @throws StorageException upon failure + */ + public Blob get(String blob, BlobSourceOption... options) { + return new Blob(storage, storage.get(info.name(), blob, options)); } - public List get(String... blob) { - // todo - return null; + /** + * Return a list of requested blobs in this bucket. Blobs that do not exist are null. + * + * @param blobNames names of the requested blobs + * @return a list containing the requested blobs in this bucket + * @throws StorageException upon failure + */ + public List getAll(String... blobNames) { + BatchRequest.Builder batch = BatchRequest.builder(); + for (String blobName : blobNames) { + batch.get(info.name(), blobName); + } + List blobs = new LinkedList<>(); + BatchResponse response = storage.apply(batch.build()); + for (BatchResponse.Result result : response.gets()) { + BlobInfo blobInfo = result.get(); + blobs.add(blobInfo != null ? new Blob(storage, blobInfo) : null); + } + return blobs; } - /* - * BlobInfo create(BlobInfo blobInfo, byte[] content, BlobTargetOption... options) { - * - * } + /** + * Create a new blob in this bucket. + * + * @param blob a blob name + * @param content the blob content + * @param options options for blob creation + * @return a complete blob information. + * @throws StorageException upon failure */ + Blob create(String blob, byte[] content, BlobTargetOption... options) { + BlobInfo blobInfo = BlobInfo.of(info.name(), blob); + return new Blob(storage, storage.create(blobInfo, content, options)); + } - + /** + * Get this bucket's {@code Storage} object. + * + * @return the storage service used by this bucket to issue requests + */ public Storage storage() { return storage; } 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 e49c5fde96c6..40ce864368df 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 @@ -41,7 +41,7 @@ public class BlobTest { private Storage storage; private Blob blob; - private BlobInfo blobInfo = BlobInfo.of("b", "n"); + private final BlobInfo blobInfo = BlobInfo.of("b", "n"); @Before public void setUp() throws Exception { @@ -107,6 +107,9 @@ public void testCopyTo() throws Exception { replay(storage); Blob targetBlob = blob.copyTo(target); assertEquals(target, targetBlob.info()); + assertEquals(capturedCopyRequest.getValue().sourceBlob(), blob.info().name()); + assertEquals(capturedCopyRequest.getValue().sourceBucket(), blob.info().bucket()); + assertEquals(capturedCopyRequest.getValue().target(), target); assertSame(storage, targetBlob.storage()); } 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 new file mode 100644 index 000000000000..4b23af708636 --- /dev/null +++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BucketTest.java @@ -0,0 +1,160 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.storage; + +import static org.easymock.EasyMock.capture; +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.gcloud.storage.BatchResponse.Result; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import org.easymock.Capture; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class BucketTest { + + private static final Iterable BLOB_INFO_RESULTS = ImmutableList.of( + BlobInfo.of("b", "n1"), + BlobInfo.of("b", "n2"), + BlobInfo.of("b", "n3")); + + private Storage storage; + private Bucket bucket; + private final BucketInfo bucketInfo = BucketInfo.of("b"); + + @Before + public void setUp() throws Exception { + storage = createStrictMock(Storage.class); + bucket = new Bucket(storage, bucketInfo); + } + + @After + public void tearDown() throws Exception { + verify(storage); + } + + @Test + public void testInfo() throws Exception { + assertEquals(bucketInfo, bucket.info()); + replay(storage); + } + + @Test + public void testExists_True() throws Exception { + expect(storage.get(bucketInfo.name())).andReturn(bucketInfo); + replay(storage); + assertTrue(bucket.exists()); + } + + @Test + public void testExists_False() throws Exception { + expect(storage.get(bucketInfo.name())).andReturn(null); + replay(storage); + assertFalse(bucket.exists()); + } + + @Test + public void testUpdate() throws Exception { + BucketInfo updatedInfo = bucketInfo.toBuilder().notFoundPage("p").build(); + expect(storage.update(updatedInfo)).andReturn(updatedInfo); + replay(storage); + bucket.update(updatedInfo); + assertSame(storage, bucket.storage()); + assertEquals(updatedInfo, bucket.info()); + } + + @Test + public void testDelete() throws Exception { + expect(storage.delete(bucketInfo.name())).andReturn(true); + replay(storage); + assertTrue(bucket.delete()); + } + + @Test + public void testList() throws Exception { + BaseListResult blobInfoResult = new BaseListResult<>(null, "c", BLOB_INFO_RESULTS); + expect(storage.list(bucketInfo.name())).andReturn(blobInfoResult); + replay(storage); + ListResult blobResult = bucket.list(); + Iterator blobInfoIterator = blobInfoResult.iterator(); + Iterator blobIterator = blobResult.iterator(); + while (blobInfoIterator.hasNext() && blobIterator.hasNext()) { + assertEquals(blobInfoIterator.next(), blobIterator.next().info()); + } + assertFalse(blobInfoIterator.hasNext()); + assertFalse(blobIterator.hasNext()); + assertEquals(blobInfoResult.nextPageCursor(), blobResult.nextPageCursor()); + } + + @Test + public void testGet() throws Exception { + BlobInfo info = BlobInfo.of("b", "n"); + expect(storage.get(bucketInfo.name(), "n")).andReturn(info); + replay(storage); + Blob blob = bucket.get("n"); + assertEquals(info, blob.info()); + } + + @Test + public void testGetAll() throws Exception { + Capture capturedBatchRequest = Capture.newInstance(); + List> batchResultList = new LinkedList<>(); + for (BlobInfo info : BLOB_INFO_RESULTS) { + batchResultList.add(new Result<>(info)); + } + BatchResponse response = + 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"); + Set blobInfoSet = capturedBatchRequest.getValue().toGet().keySet(); + assertEquals(batchResultList.size(), blobInfoSet.size()); + for (BlobInfo info : BLOB_INFO_RESULTS) { + assertTrue(blobInfoSet.contains(info)); + } + Iterator blobIterator = blobs.iterator(); + Iterator> batchResultIterator = response.gets().iterator(); + while (batchResultIterator.hasNext() && blobIterator.hasNext()) { + assertEquals(batchResultIterator.next().get(), blobIterator.next().info()); + } + assertFalse(batchResultIterator.hasNext()); + assertFalse(blobIterator.hasNext()); + } + + @Test + public void testCreate() throws Exception { + BlobInfo info = BlobInfo.of("b", "n"); + byte[] content = {0xD, 0xE, 0xA, 0xD}; + expect(storage.create(info, content)).andReturn(info); + replay(storage); + Blob blob = bucket.create("n", content); + assertEquals(info, blob.info()); + } +}