From 3320944ad919b6c12835669c5bd25debf8b96a5a Mon Sep 17 00:00:00 2001 From: ozarov Date: Mon, 1 Jun 2015 21:39:34 -0700 Subject: [PATCH 1/4] initial work to create blob and bucket --- .../java/com/google/gcloud/storage/Blob.java | 41 +++++++++++++++++ .../com/google/gcloud/storage/BlobInfo.java | 2 +- .../com/google/gcloud/storage/Bucket.java | 44 +++++++++++++++++++ .../com/google/gcloud/storage/BucketInfo.java | 2 +- 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 gcloud-java-storage/src/main/java/com/google/gcloud/storage/Blob.java create mode 100644 gcloud-java-storage/src/main/java/com/google/gcloud/storage/Bucket.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 new file mode 100644 index 000000000000..1362fed61bd2 --- /dev/null +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Blob.java @@ -0,0 +1,41 @@ +/* + * 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 com.google.common.base.Preconditions.checkNotNull; + +/** + * A Google cloud storage object. + */ +public class Blob { + + private final Storage storage; + private final BlobInfo info; + + Blob(Storage storage, BlobInfo info) { + this.storage = checkNotNull(storage); + this.info = checkNotNull(info); + } + + public BlobInfo info() { + return info; + } + + public byte[] content(Storage.BlobSourceOption... options) { + return storage.load(info.bucket(), info.name(), options); + } +} 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 3ef01af8d1f3..47ce45b4decb 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 @@ -37,7 +37,7 @@ import java.util.Objects; /** - * A Google Storage object. + * Google Storage object metadata. * * @see Concepts and Terminology */ 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 new file mode 100644 index 000000000000..c8593f79fa73 --- /dev/null +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Bucket.java @@ -0,0 +1,44 @@ +/* + * 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 com.google.common.base.Preconditions.checkNotNull; + +import java.util.List; + +/** + * A Google cloud storage bucket. + */ +public class Bucket { + + private final Storage storage; + private final BucketInfo info; + + Bucket(Storage storage, BucketInfo info) { + this.storage = checkNotNull(storage); + this.info = checkNotNull(info); + } + + public ListResult list(Storage.BlobListOption... options) { + return storage.list(info.name(), options); + } + + public List get(String... blob) { + // todo + return null; + } +} diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BucketInfo.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BucketInfo.java index 60926e01dbb2..f43700db3b01 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BucketInfo.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BucketInfo.java @@ -44,7 +44,7 @@ import java.util.Objects; /** - * A Google Storage bucket. + * Google Storage bucket metadata; * * @see Concepts and Terminology */ From 6d4f713bf76d5108272c95f44dbb5b5e411d84fd Mon Sep 17 00:00:00 2001 From: aozarov Date: Wed, 3 Jun 2015 12:40:32 -0700 Subject: [PATCH 2/4] fix after merge --- .../src/main/java/com/google/gcloud/storage/Blob.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1362fed61bd2..71d50a79ca93 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 @@ -36,6 +36,6 @@ public BlobInfo info() { } public byte[] content(Storage.BlobSourceOption... options) { - return storage.load(info.bucket(), info.name(), options); + return storage.readAllBytes(info.bucket(), info.name(), options); } } From 5c3925e8fb27890357d3c354611fccba0955516c Mon Sep 17 00:00:00 2001 From: aozarov Date: Tue, 9 Jun 2015 16:26:37 -0700 Subject: [PATCH 3/4] add Blob functions and test --- .../java/com/google/gcloud/storage/Blob.java | 107 +++++++++++++- .../com/google/gcloud/storage/Storage.java | 16 ++ .../com/google/gcloud/storage/BlobTest.java | 139 ++++++++++++++++++ 3 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobTest.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 71d50a79ca93..a1001d01e2a8 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 @@ -18,13 +18,21 @@ import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.collect.ImmutableList; +import com.google.gcloud.storage.Storage.BlobSourceOption; +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; + /** * A Google cloud storage object. */ public class Blob { private final Storage storage; - private final BlobInfo info; + private BlobInfo info; Blob(Storage storage, BlobInfo info) { this.storage = checkNotNull(storage); @@ -35,7 +43,104 @@ public BlobInfo info() { return info; } + /** + * Returns true if this blob exists. + * + * @throws StorageException upon failure + */ + public boolean exists(BlobSourceOption... options) { + return storage.get(info.bucket(), info.name(), options) != null; + } + + /** + * Returns the blob's content. + * + * @throws StorageException upon failure + */ public byte[] content(Storage.BlobSourceOption... options) { return storage.readAllBytes(info.bucket(), info.name(), options); } + + + /** + * Updates the blob's information. + * + * @throws StorageException upon failure + */ + public void update(BlobInfo blobInfo, BlobTargetOption... options) { + info = storage.update(blobInfo, options); + } + + /** + * Deletes this blob. + * + * @return true if bucket was deleted + * @throws StorageException upon failure + */ + public boolean delete(BlobSourceOption... options) { + return storage.delete(info.bucket(), info.name(), options); + } + + /** + * Send a copy request. + * + * @return the copied blob. + * @throws StorageException upon failure + */ + public Blob copyTo(BlobInfo target, BlobSourceOption... options) { + return copyTo(target, ImmutableList.copyOf(options), ImmutableList.of()); + } + + /** + * Send a copy request. + * + * @return the copied blob. + * @throws StorageException upon failure + */ + public Blob copyTo(BlobInfo target, Iterable sourceOptions, + Iterable targetOptions) { + CopyRequest copyRequest = CopyRequest.builder() + .source(info.bucket(), info.name()) + .sourceOptions(sourceOptions) + .target(target) + .targetOptions(targetOptions) + .build(); + return new Blob(storage, storage.copy(copyRequest)); + } + + /** + * Returns a channel for reading this blob's content. + * + * @throws StorageException upon failure + */ + public BlobReadChannel reader(BlobSourceOption... options) { + return storage.reader(info.bucket(), info.name(), options); + } + + /** + * Returns a channel for writing to this blob. + * + * @throws StorageException upon failure + */ + public BlobWriteChannel writer(BlobTargetOption... options) { + return storage.writer(info, options); + } + + /** + * Generates 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) + * @see Signed-URLs + */ + public URL signUrl(long expirationTimeInSeconds, SignUrlOption... options) { + return storage.signUrl(info, expirationTimeInSeconds, options); + } + + public Storage storage() { + return storage; + } } 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 e3cfbc195860..d8e73fc8bb5c 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 @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.gcloud.AuthCredentials.ServiceAccountAuthCredentials; import com.google.gcloud.Service; import com.google.gcloud.spi.StorageRpc; @@ -342,6 +343,11 @@ public Builder targetOptions(BlobTargetOption... options) { return this; } + public Builder targetOptions(Iterable options) { + Iterables.addAll(targetOptions, options); + return this; + } + public ComposeRequest build() { checkArgument(!sourceBlobs.isEmpty()); checkNotNull(target); @@ -409,6 +415,11 @@ public Builder sourceOptions(BlobSourceOption... options) { return this; } + public Builder sourceOptions(Iterable options) { + Iterables.addAll(sourceOptions, options); + return this; + } + public Builder target(BlobInfo target) { this.target = target; return this; @@ -419,6 +430,11 @@ public Builder targetOptions(BlobTargetOption... options) { return this; } + public Builder targetOptions(Iterable options) { + Iterables.addAll(targetOptions, options); + return this; + } + public CopyRequest build() { checkNotNull(sourceBucket); checkNotNull(sourceBlob); 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 new file mode 100644 index 000000000000..f113dfc308a7 --- /dev/null +++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BlobTest.java @@ -0,0 +1,139 @@ +/* + * + * * 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.createMock; +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.assertArrayEquals; +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.gcloud.storage.Storage.CopyRequest; + +import org.easymock.Capture; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.net.URL; + +public class BlobTest { + + private Storage storage; + private Blob blob; + private BlobInfo blobInfo = BlobInfo.of("b", "n"); + + @Before + public void setUp() throws Exception { + storage = createStrictMock(Storage.class); + blob = new Blob(storage, blobInfo); + } + + @After + public void tearDown() throws Exception { + verify(storage); + } + + @Test + public void testInfo() throws Exception { + assertEquals(blobInfo, blob.info()); + replay(storage); + } + + @Test + public void testExists_True() throws Exception { + expect(storage.get(blobInfo.bucket(), blobInfo.name())).andReturn(blobInfo); + replay(storage); + assertTrue(blob.exists()); + } + + @Test + public void testExists_False() throws Exception { + expect(storage.get(blobInfo.bucket(), blobInfo.name())).andReturn(null); + replay(storage); + assertFalse(blob.exists()); + } + + @Test + public void testContent() throws Exception { + byte[] content = {1, 2}; + expect(storage.readAllBytes(blobInfo.bucket(), blobInfo.name())).andReturn(content); + replay(storage); + assertArrayEquals(content, blob.content()); + } + + @Test + public void testUpdate() throws Exception { + BlobInfo updatedInfo = blobInfo.toBuilder().cacheControl("c").build(); + expect(storage.update(updatedInfo)).andReturn(updatedInfo); + replay(storage); + blob.update(updatedInfo); + assertSame(storage, blob.storage()); + assertEquals(updatedInfo, blob.info()); + } + + @Test + public void testDelete() throws Exception { + expect(storage.delete(blobInfo.bucket(), blobInfo.name())).andReturn(true); + replay(storage); + assertTrue(blob.delete()); + } + + @Test + public void testCopyTo() throws Exception { + BlobInfo target = BlobInfo.of("bt", "nt"); + Capture capturedCopyRequest = Capture.newInstance(); + expect(storage.copy(capture(capturedCopyRequest))).andReturn(target); + replay(storage); + Blob targetBlob = blob.copyTo(target); + assertEquals(target, targetBlob.info()); + assertSame(storage, targetBlob.storage()); + } + + @Test + public void testReader() throws Exception { + BlobReadChannel channel = createMock(BlobReadChannel.class); + expect(storage.reader(blobInfo.bucket(), blobInfo.name())).andReturn(channel); + replay(storage); + assertSame(channel, blob.reader()); + } + + @Test + public void testWriter() throws Exception { + BlobWriteChannel channel = createMock(BlobWriteChannel.class); + expect(storage.writer(blobInfo)).andReturn(channel); + replay(storage); + assertSame(channel, blob.writer()); + } + + @Test + public void testSignUrl() throws Exception { + URL url = new URL("http://localhost:123/bla"); + expect(storage.signUrl(blobInfo, 100)).andReturn(url); + replay(storage); + assertEquals(url, blob.signUrl(100)); + } +} From 7f76853030f0a9a0ee1f5653753d03ea88055385 Mon Sep 17 00:00:00 2001 From: aozarov Date: Thu, 11 Jun 2015 17:47:11 -0700 Subject: [PATCH 4/4] functional blob/bucket - work in progress --- .../java/com/google/gcloud/storage/Blob.java | 74 ++++++++++++++++--- .../com/google/gcloud/storage/Bucket.java | 56 +++++++++++++- 2 files changed, 118 insertions(+), 12 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 a1001d01e2a8..f691c55385f8 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 @@ -16,10 +16,13 @@ package com.google.gcloud.storage; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.gcloud.storage.Blob.BlobSourceOption.convert; import com.google.common.collect.ImmutableList; -import com.google.gcloud.storage.Storage.BlobSourceOption; +import com.google.common.collect.Iterables; +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; @@ -29,12 +32,61 @@ /** * A Google cloud storage object. */ -public class Blob { +public final class Blob { private final Storage storage; private BlobInfo info; - Blob(Storage storage, BlobInfo info) { + public static class BlobSourceOption extends Option { + + private static final long serialVersionUID = 214616862061934846L; + + private BlobSourceOption(StorageRpc.Option rpcOption) { + super(rpcOption, null); + } + + public static BlobSourceOption generationMatch() { + return new BlobSourceOption(StorageRpc.Option.IF_GENERATION_MATCH); + } + + public static BlobSourceOption generationNotMatch() { + return new BlobSourceOption(StorageRpc.Option.IF_GENERATION_NOT_MATCH); + } + + public static BlobSourceOption metagenerationMatch() { + return new BlobSourceOption(StorageRpc.Option.IF_METAGENERATION_MATCH); + } + + public static BlobSourceOption metagenerationNotMatch() { + return new BlobSourceOption(StorageRpc.Option.IF_METAGENERATION_NOT_MATCH); + } + + private Storage.BlobSourceOption convert(BlobInfo blobInfo) { + switch (rpcOption()) { + case IF_GENERATION_MATCH: + return Storage.BlobSourceOption.generationMatch(blobInfo.generation()); + case IF_GENERATION_NOT_MATCH: + return Storage.BlobSourceOption.generationNotMatch(blobInfo.generation()); + case IF_METAGENERATION_MATCH: + return Storage.BlobSourceOption.metagenerationMatch(blobInfo.metageneration()); + case IF_METAGENERATION_NOT_MATCH: + return Storage.BlobSourceOption.metagenerationNotMatch(blobInfo.metageneration()); + default: + throw new AssertionError("Unexpected enum value"); + } + } + + static Storage.BlobSourceOption[] convert(BlobInfo blobInfo, BlobSourceOption... options) { + Storage.BlobSourceOption[] convertedOptions = new Storage.BlobSourceOption[options.length]; + int index = 0; + for (BlobSourceOption option : options) { + convertedOptions[index++] = option.convert(blobInfo); + } + return convertedOptions; + } + } + + public Blob(Storage storage, BlobInfo info) { this.storage = checkNotNull(storage); this.info = checkNotNull(info); } @@ -48,8 +100,8 @@ public BlobInfo info() { * * @throws StorageException upon failure */ - public boolean exists(BlobSourceOption... options) { - return storage.get(info.bucket(), info.name(), options) != null; + public boolean exists() { + return storage.get(info.bucket(), info.name()) != null; } /** @@ -61,13 +113,17 @@ public byte[] content(Storage.BlobSourceOption... options) { return storage.readAllBytes(info.bucket(), info.name(), options); } - /** * Updates 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. * * @throws StorageException upon failure */ public void update(BlobInfo blobInfo, BlobTargetOption... options) { + checkArgument(blobInfo.bucket() == info.bucket(), "Bucket name must match"); + checkArgument(blobInfo.name() == info.name(), "Blob name must match"); info = storage.update(blobInfo, options); } @@ -78,7 +134,7 @@ public void update(BlobInfo blobInfo, BlobTargetOption... options) { * @throws StorageException upon failure */ public boolean delete(BlobSourceOption... options) { - return storage.delete(info.bucket(), info.name(), options); + return storage.delete(info.bucket(), info.name(), convert(info, options)); } /** @@ -101,7 +157,7 @@ public Blob copyTo(BlobInfo target, Iterable sourceOptions, Iterable targetOptions) { CopyRequest copyRequest = CopyRequest.builder() .source(info.bucket(), info.name()) - .sourceOptions(sourceOptions) + .sourceOptions(convert(info, Iterables.toArray(sourceOptions, BlobSourceOption.class))) .target(target) .targetOptions(targetOptions) .build(); @@ -114,7 +170,7 @@ public Blob copyTo(BlobInfo target, Iterable sourceOptions, * @throws StorageException upon failure */ public BlobReadChannel reader(BlobSourceOption... options) { - return storage.reader(info.bucket(), info.name(), options); + return storage.reader(info.bucket(), info.name(), convert(info, options)); } /** 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 c8593f79fa73..ebdf6456f36f 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 @@ -16,29 +16,79 @@ package com.google.gcloud.storage; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.gcloud.storage.Storage.BlobSourceOption; +import com.google.gcloud.storage.Storage.BucketSourceOption; +import com.google.gcloud.storage.Storage.BucketTargetOption; + import java.util.List; /** * A Google cloud storage bucket. */ -public class Bucket { +public final class Bucket { private final Storage storage; - private final BucketInfo info; + private BucketInfo info; - Bucket(Storage storage, BucketInfo info) { + public Bucket(Storage storage, BucketInfo info) { this.storage = checkNotNull(storage); this.info = checkNotNull(info); } + /** + * Returns true if this bucket exists. + * + * @throws StorageException upon failure + */ + public boolean exists() { + return storage.get(info.name()) != null; + } + + /** + * Update the bucket's information. + * Bucket's name cannot be changed. + * + * @throws StorageException upon failure + */ + public void update(BucketInfo bucketInfo, BucketTargetOption... options) { + checkArgument(bucketInfo.name() == info.name(), "Bucket name must match"); + info = storage.update(bucketInfo, options); + } + + /** + * Delete this bucket. + * + * @return true if bucket was deleted + * @throws StorageException upon failure + */ + public boolean delete(BucketSourceOption... options) { + return storage.delete(info.name(), options); + } + public ListResult list(Storage.BlobListOption... options) { return storage.list(info.name(), options); } + public BlobInfo get(String blob, BlobSourceOption... options) { + return null; + } + public List get(String... blob) { // todo return null; } + + /* + BlobInfo create(BlobInfo blobInfo, byte[] content, BlobTargetOption... options) { + + } +*/ + + + public Storage storage() { + return storage; + } }