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..f691c55385f8 --- /dev/null +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Blob.java @@ -0,0 +1,202 @@ +/* + * 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.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.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; + +import java.net.URL; + +/** + * A Google cloud storage object. + */ +public final class Blob { + + private final Storage storage; + private 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); + } + + public BlobInfo info() { + return info; + } + + /** + * Returns true if this blob exists. + * + * @throws StorageException upon failure + */ + public boolean exists() { + return storage.get(info.bucket(), info.name()) != 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. + * 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); + } + + /** + * 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(), convert(info, 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(convert(info, Iterables.toArray(sourceOptions, BlobSourceOption.class))) + .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(), convert(info, 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/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..ebdf6456f36f --- /dev/null +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/Bucket.java @@ -0,0 +1,94 @@ +/* + * 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.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 final class Bucket { + + private final Storage storage; + private 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; + } +} 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 437ce1a142d5..9929228c50d4 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 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)); + } +}