diff --git a/README.md b/README.md index df68cd18005d..a534d798bbca 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,7 @@ Here is a code snippet showing a simple usage example from within Compute/App En import static java.nio.charset.StandardCharsets.UTF_8; import com.google.gcloud.storage.Blob; +import com.google.gcloud.storage.BlobInfo; import com.google.gcloud.storage.BlobId; import com.google.gcloud.storage.Storage; import com.google.gcloud.storage.StorageOptions; @@ -257,7 +258,7 @@ import java.nio.channels.WritableByteChannel; Storage storage = StorageOptions.defaultInstance().service(); BlobId blobId = BlobId.of("bucket", "blob_name"); -Blob blob = Blob.get(storage, blobId); +Blob blob = storage.get(blobId); if (blob == null) { BlobInfo blobInfo = BlobInfo.builder(blobId).contentType("text/plain").build(); storage.create(blobInfo, "Hello, Cloud Storage!".getBytes(UTF_8)); diff --git a/gcloud-java-examples/src/main/java/com/google/gcloud/examples/StorageExample.java b/gcloud-java-examples/src/main/java/com/google/gcloud/examples/StorageExample.java index e3bee626f49c..a331543af001 100644 --- a/gcloud-java-examples/src/main/java/com/google/gcloud/examples/StorageExample.java +++ b/gcloud-java-examples/src/main/java/com/google/gcloud/examples/StorageExample.java @@ -25,7 +25,6 @@ import com.google.gcloud.storage.BlobId; import com.google.gcloud.storage.BlobInfo; import com.google.gcloud.storage.Bucket; -import com.google.gcloud.storage.BucketInfo; import com.google.gcloud.storage.CopyWriter; import com.google.gcloud.storage.Storage; import com.google.gcloud.storage.Storage.ComposeRequest; @@ -133,27 +132,27 @@ public void run(Storage storage, BlobId... blobIds) { if (blobIds.length == 1) { if (blobIds[0].name().isEmpty()) { // get Bucket - Bucket bucket = Bucket.get(storage, blobIds[0].bucket()); + Bucket bucket = storage.get(blobIds[0].bucket()); if (bucket == null) { System.out.println("No such bucket"); return; } - System.out.println("Bucket info: " + bucket.info()); + System.out.println("Bucket info: " + bucket); } else { // get Blob - Blob blob = Blob.get(storage, blobIds[0]); + Blob blob = storage.get(blobIds[0]); if (blob == null) { System.out.println("No such object"); return; } - System.out.println("Blob info: " + blob.info()); + System.out.println("Blob info: " + blob); } } else { // use batch to get multiple blobs. - List blobs = Blob.get(storage, Arrays.asList(blobIds)); + List blobs = storage.get(blobIds); for (Blob blob : blobs) { if (blob != null) { - System.out.println(blob.info()); + System.out.println(blob); } } } @@ -184,7 +183,7 @@ private static class DeleteAction extends BlobsAction { @Override public void run(Storage storage, BlobId... blobIds) { // use batch operation - List deleteResults = Blob.delete(storage, blobIds); + List deleteResults = storage.delete(blobIds); int index = 0; for (Boolean deleted : deleteResults) { if (deleted) { @@ -218,20 +217,20 @@ String parse(String... args) { public void run(Storage storage, String bucketName) { if (bucketName == null) { // list buckets - Iterator bucketInfoIterator = storage.list().iterateAll(); - while (bucketInfoIterator.hasNext()) { - System.out.println(bucketInfoIterator.next()); + Iterator bucketIterator = storage.list().iterateAll(); + while (bucketIterator.hasNext()) { + System.out.println(bucketIterator.next()); } } else { // list a bucket's blobs - Bucket bucket = Bucket.get(storage, bucketName); + Bucket bucket = storage.get(bucketName); if (bucket == null) { System.out.println("No such bucket"); return; } Iterator blobIterator = bucket.list().iterateAll(); while (blobIterator.hasNext()) { - System.out.println(blobIterator.next().info()); + System.out.println(blobIterator.next()); } } } @@ -257,8 +256,7 @@ private void run(Storage storage, Path uploadFrom, BlobInfo blobInfo) throws IOE if (Files.size(uploadFrom) > 1_000_000) { // When content is not available or large (1MB or more) it is recommended // to write it in chunks via the blob's channel writer. - Blob blob = new Blob(storage, blobInfo); - try (WriteChannel writer = blob.writer()) { + try (WriteChannel writer = storage.writer(blobInfo)) { byte[] buffer = new byte[1024]; try (InputStream input = Files.newInputStream(uploadFrom)) { int limit; @@ -311,7 +309,7 @@ public void run(Storage storage, Tuple tuple) throws IOException { } private void run(Storage storage, BlobId blobId, Path downloadTo) throws IOException { - Blob blob = Blob.get(storage, blobId); + Blob blob = storage.get(blobId); if (blob == null) { System.out.println("No such object"); return; @@ -320,7 +318,7 @@ private void run(Storage storage, BlobId blobId, Path downloadTo) throws IOExcep if (downloadTo != null) { writeTo = new PrintStream(new FileOutputStream(downloadTo.toFile())); } - if (blob.info().size() < 1_000_000) { + if (blob.size() < 1_000_000) { // Blob is small read all its content in one request byte[] content = blob.content(); writeTo.write(content); @@ -438,13 +436,13 @@ public void run(Storage storage, Tuple> tuple) } private void run(Storage storage, BlobId blobId, Map metadata) { - Blob blob = Blob.get(storage, blobId); + Blob blob = storage.get(blobId); if (blob == null) { System.out.println("No such object"); return; } - Blob updateBlob = blob.update(blob.info().toBuilder().metadata(metadata).build()); - System.out.println("Updated " + updateBlob.info()); + Blob updateBlob = blob.toBuilder().metadata(metadata).build().update(); + System.out.println("Updated " + updateBlob); } @Override @@ -488,9 +486,8 @@ public void run(Storage storage, Tuple run(storage, tuple.x(), tuple.y()); } - private void run(Storage storage, ServiceAccountAuthCredentials cred, BlobInfo blobInfo) - throws IOException { - Blob blob = new Blob(storage, blobInfo); + private void run(Storage storage, ServiceAccountAuthCredentials cred, BlobInfo blobInfo) { + Blob blob = storage.get(blobInfo.blobId()); System.out.println("Signed URL: " + blob.signUrl(1, TimeUnit.DAYS, SignUrlOption.serviceAccount(cred))); } diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java index f12a7ea50676..4d12a31274c0 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Project.java @@ -38,14 +38,12 @@ public class Project extends ProjectInfo { private final ResourceManagerOptions options; private transient ResourceManager resourceManager; + /** + * Builder for {@code Project}. + */ public static class Builder extends ProjectInfo.Builder { private final ResourceManager resourceManager; - private ProjectInfo.BuilderImpl infoBuilder; - - Builder(ResourceManager resourceManager) { - this.resourceManager = resourceManager; - this.infoBuilder = new ProjectInfo.BuilderImpl(); - } + private final ProjectInfo.BuilderImpl infoBuilder; Builder(Project project) { this.resourceManager = project.resourceManager; @@ -124,16 +122,6 @@ public Project build() { this.options = resourceManager.options(); } - /** - * Constructs a Project object that contains project information got from the server. - * - * @return Project object containing the project's metadata or {@code null} if not found - * @throws ResourceManagerException upon failure - */ - public static Project get(ResourceManager resourceManager, String projectId) { - return resourceManager.get(projectId); - } - /** * Returns the {@link ResourceManager} service object associated with this Project. */ @@ -142,14 +130,14 @@ public ResourceManager resourceManager() { } /** - * Fetches the current project's latest information. Returns {@code null} if the job does not + * Fetches the project's latest information. Returns {@code null} if the project does not * exist. * * @return Project containing the project's updated metadata or {@code null} if not found * @throws ResourceManagerException upon failure */ public Project reload() { - return Project.get(resourceManager, projectId()); + return resourceManager.get(projectId()); } /** @@ -210,10 +198,6 @@ public Project replace() { return resourceManager.replace(this); } - static Builder builder(ResourceManager resourceManager, String projectId) { - return new Builder(resourceManager).projectId(projectId); - } - @Override public Builder toBuilder() { return new Builder(this); diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ProjectInfo.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ProjectInfo.java index 7553a207cd29..260e8a8e2f26 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ProjectInfo.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ProjectInfo.java @@ -115,7 +115,10 @@ static ResourceId fromPb( } } - public static abstract class Builder { + /** + * Builder for {@code ProjectInfo}. + */ + public abstract static class Builder { /** * Set the user-assigned name of the project. @@ -184,7 +187,9 @@ static class BuilderImpl extends Builder { private Long createTimeMillis; private ResourceId parent; - BuilderImpl() {} + BuilderImpl(String projectId) { + this.projectId = projectId; + } BuilderImpl(ProjectInfo info) { this.name = info.name; @@ -331,7 +336,7 @@ public Long createTimeMillis() { @Override public boolean equals(Object obj) { - return obj.getClass().equals(ProjectInfo.class) + return obj != null && obj.getClass().equals(ProjectInfo.class) && Objects.equals(toPb(), ((ProjectInfo) obj).toPb()); } @@ -341,7 +346,7 @@ public int hashCode() { } public static Builder builder(String id) { - return new BuilderImpl().projectId(id); + return new BuilderImpl(id); } public Builder toBuilder() { diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java index a741963913c6..4e239acc45ef 100644 --- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java +++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ProjectTest.java @@ -24,11 +24,11 @@ import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; import com.google.common.collect.ImmutableMap; import org.junit.After; +import org.junit.Before; import org.junit.Test; import java.util.Map; @@ -54,6 +54,11 @@ public class ProjectTest { private Project expectedProject; private Project project; + @Before + public void setUp() { + resourceManager = createStrictMock(ResourceManager.class); + } + @After public void tearDown() throws Exception { verify(resourceManager); @@ -62,7 +67,6 @@ public void tearDown() throws Exception { private void initializeExpectedProject(int optionsCalls) { expect(serviceMockReturnsOptions.options()).andReturn(mockOptions).times(optionsCalls); replay(serviceMockReturnsOptions); - resourceManager = createStrictMock(ResourceManager.class); expectedProject = new Project(serviceMockReturnsOptions, new ProjectInfo.BuilderImpl(PROJECT_INFO)); } @@ -71,31 +75,33 @@ private void initializeProject() { project = new Project(resourceManager, new ProjectInfo.BuilderImpl(PROJECT_INFO)); } + @Test + public void testToBuilder() { + initializeExpectedProject(4); + replay(resourceManager); + compareProjects(expectedProject, expectedProject.toBuilder().build()); + } + @Test public void testBuilder() { - initializeExpectedProject(2); + initializeExpectedProject(4); + expect(resourceManager.options()).andReturn(mockOptions).times(4); replay(resourceManager); - Project builtProject = Project.builder(serviceMockReturnsOptions, PROJECT_ID) - .name(NAME) + Project.Builder builder = + new Project.Builder(new Project(resourceManager, new ProjectInfo.BuilderImpl(PROJECT_ID))); + Project project = builder.name(NAME) .labels(LABELS) .projectNumber(PROJECT_NUMBER) .createTimeMillis(CREATE_TIME_MILLIS) .state(STATE) .build(); - assertEquals(PROJECT_ID, builtProject.projectId()); - assertEquals(NAME, builtProject.name()); - assertEquals(LABELS, builtProject.labels()); - assertEquals(PROJECT_NUMBER, builtProject.projectNumber()); - assertEquals(CREATE_TIME_MILLIS, builtProject.createTimeMillis()); - assertEquals(STATE, builtProject.state()); - assertSame(serviceMockReturnsOptions, builtProject.resourceManager()); - } - - @Test - public void testToBuilder() { - initializeExpectedProject(4); - replay(resourceManager); - compareProjects(expectedProject, expectedProject.toBuilder().build()); + assertEquals(PROJECT_ID, project.projectId()); + assertEquals(NAME, project.name()); + assertEquals(LABELS, project.labels()); + assertEquals(PROJECT_NUMBER, project.projectNumber()); + assertEquals(CREATE_TIME_MILLIS, project.createTimeMillis()); + assertEquals(STATE, project.state()); + assertEquals(resourceManager.options(), project.resourceManager().options()); } @Test @@ -103,7 +109,7 @@ public void testGet() { initializeExpectedProject(1); expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(expectedProject); replay(resourceManager); - Project loadedProject = Project.get(resourceManager, PROJECT_INFO.projectId()); + Project loadedProject = resourceManager.get(PROJECT_INFO.projectId()); assertEquals(expectedProject, loadedProject); } @@ -126,7 +132,7 @@ public void testLoadNull() { initializeExpectedProject(1); expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(null); replay(resourceManager); - assertNull(Project.get(resourceManager, PROJECT_INFO.projectId())); + assertNull(resourceManager.get(PROJECT_INFO.projectId())); } @Test diff --git a/gcloud-java-storage/README.md b/gcloud-java-storage/README.md index 7260ab5fe5c5..d1a389919558 100644 --- a/gcloud-java-storage/README.md +++ b/gcloud-java-storage/README.md @@ -77,15 +77,17 @@ Storage storage = StorageOptions.defaultInstance().service(); For other authentication options, see the [Authentication](https://github.com/GoogleCloudPlatform/gcloud-java#authentication) page. #### Storing data -Stored objects are called "blobs" in `gcloud-java` and are organized into containers called "buckets". In this code snippet, we will create a new bucket and upload a blob to that bucket. +Stored objects are called "blobs" in `gcloud-java` and are organized into containers called "buckets". `Blob`, a subclass of `BlobInfo`, adds a layer of service-related functionality over `BlobInfo`. Similarly, `Bucket` adds a layer of service-related functionality over `BucketInfo`. In this code snippet, we will create a new bucket and upload a blob to that bucket. Add the following imports at the top of your file: ```java import static java.nio.charset.StandardCharsets.UTF_8; +import com.google.gcloud.storage.Blob; import com.google.gcloud.storage.BlobId; import com.google.gcloud.storage.BlobInfo; +import com.google.gcloud.storage.Bucket; import com.google.gcloud.storage.BucketInfo; ``` @@ -96,11 +98,11 @@ Then add the following code to create a bucket and upload a simple blob. ```java // Create a bucket String bucketName = "my_unique_bucket"; // Change this to something unique -BucketInfo bucketInfo = storage.create(BucketInfo.of(bucketName)); +Bucket bucket = storage.create(BucketInfo.of(bucketName)); // Upload a blob to the newly created bucket BlobId blobId = BlobId.of(bucketName, "my_blob_name"); -BlobInfo blobInfo = storage.create( +Blob blob = storage.create( BlobInfo.builder(blobId).contentType("text/plain").build(), "a simple blob".getBytes(UTF_8)); ``` @@ -125,14 +127,14 @@ Then add the following code to list all your buckets and all the blobs inside yo ```java // List all your buckets -Iterator bucketInfoIterator = storage.list().iterateAll(); +Iterator bucketIterator = storage.list().iterateAll(); System.out.println("My buckets:"); -while (bucketInfoIterator.hasNext()) { - System.out.println(bucketInfoIterator.next()); +while (bucketIterator.hasNext()) { + System.out.println(bucketIterator.next()); } // List the blobs in a particular bucket -Iterator blobIterator = storage.list(bucketName).iterateAll(); +Iterator blobIterator = storage.list(bucketName).iterateAll(); System.out.println("My blobs:"); while (blobIterator.hasNext()) { System.out.println(blobIterator.next()); @@ -146,8 +148,10 @@ Here we put together all the code shown above into one program. This program as ```java import static java.nio.charset.StandardCharsets.UTF_8; +import com.google.gcloud.storage.Blob; import com.google.gcloud.storage.BlobId; import com.google.gcloud.storage.BlobInfo; +import com.google.gcloud.storage.Bucket; import com.google.gcloud.storage.BucketInfo; import com.google.gcloud.storage.Storage; import com.google.gcloud.storage.StorageOptions; @@ -163,11 +167,11 @@ public class GcloudStorageExample { // Create a bucket String bucketName = "my_unique_bucket"; // Change this to something unique - BucketInfo bucketInfo = storage.create(BucketInfo.of(bucketName)); + Bucket bucket = storage.create(BucketInfo.of(bucketName)); // Upload a blob to the newly created bucket BlobId blobId = BlobId.of(bucketName, "my_blob_name"); - BlobInfo blobInfo = storage.create( + Blob blob = storage.create( BlobInfo.builder(blobId).contentType("text/plain").build(), "a simple blob".getBytes(UTF_8)); @@ -175,14 +179,14 @@ public class GcloudStorageExample { String blobContent = new String(storage.readAllBytes(blobId), UTF_8); // List all your buckets - Iterator bucketInfoIterator = storage.list().iterateAll(); + Iterator bucketIterator = storage.list().iterateAll(); System.out.println("My buckets:"); - while (bucketInfoIterator.hasNext()) { - System.out.println(bucketInfoIterator.next()); + while (bucketIterator.hasNext()) { + System.out.println(bucketIterator.next()); } // List the blobs in a particular bucket - Iterator blobIterator = storage.list(bucketName).iterateAll(); + Iterator blobIterator = storage.list(bucketName).iterateAll(); System.out.println("My blobs:"); while (blobIterator.hasNext()) { System.out.println(blobIterator.next()); diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BatchResponse.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BatchResponse.java index 98e7ce09cef0..fe5f6f5743c8 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BatchResponse.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/BatchResponse.java @@ -31,8 +31,8 @@ public final class BatchResponse implements Serializable { private static final long serialVersionUID = 1057416839397037706L; private final List> deleteResult; - private final List> updateResult; - private final List> getResult; + private final List> updateResult; + private final List> getResult; public static class Result implements Serializable { @@ -113,8 +113,8 @@ static Result empty() { } } - BatchResponse(List> deleteResult, List> updateResult, - List> getResult) { + BatchResponse(List> deleteResult, List> updateResult, + List> getResult) { this.deleteResult = ImmutableList.copyOf(deleteResult); this.updateResult = ImmutableList.copyOf(updateResult); this.getResult = ImmutableList.copyOf(getResult); @@ -146,14 +146,14 @@ public List> deletes() { /** * Returns the results for the update operations using the request order. */ - public List> updates() { + public List> updates() { return updateResult; } /** * Returns the results for the get operations using the request order. */ - public List> gets() { + public List> gets() { return getResult; } } 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 fe65f6ee010b..ec8ab361328e 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,26 +16,27 @@ 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.toGetOptions; import static com.google.gcloud.storage.Blob.BlobSourceOption.toSourceOptions; +import com.google.api.services.storage.model.StorageObject; import com.google.common.base.Function; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import com.google.gcloud.ReadChannel; import com.google.gcloud.WriteChannel; import com.google.gcloud.spi.StorageRpc; +import com.google.gcloud.spi.StorageRpc.Tuple; import com.google.gcloud.storage.Storage.BlobTargetOption; import com.google.gcloud.storage.Storage.BlobWriteOption; import com.google.gcloud.storage.Storage.CopyRequest; import com.google.gcloud.storage.Storage.SignUrlOption; +import java.io.IOException; +import java.io.ObjectInputStream; import java.net.URL; import java.util.Arrays; -import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -44,13 +45,24 @@ * *

Objects of this class are immutable. Operations that modify the blob like {@link #update} and * {@link #copyTo} return a new object. To get a {@code Blob} object with the most recent - * information use {@link #reload}. + * information use {@link #reload}. {@code Blob} adds a layer of service-related functionality over + * {@link BlobInfo}. *

*/ -public final class Blob { +public final class Blob extends BlobInfo { - private final Storage storage; - private final BlobInfo info; + private static final long serialVersionUID = -6806832496717441434L; + + private final StorageOptions options; + private transient Storage storage; + + static final Function, Blob> BLOB_FROM_PB_FUNCTION = + new Function, Blob>() { + @Override + public Blob apply(Tuple pb) { + return Blob.fromPb(pb.x(), pb.y()); + } + }; /** * Class for specifying blob source options when {@code Blob} methods are used. @@ -146,60 +158,148 @@ static Storage.BlobGetOption[] toGetOptions(BlobInfo blobInfo, BlobSourceOption. } /** - * Constructs 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 + * Builder for {@code Blob}. */ - public Blob(Storage storage, BlobInfo info) { - this.storage = checkNotNull(storage); - this.info = checkNotNull(info); - } + public static class Builder extends BlobInfo.Builder { - /** - * Creates a {@code Blob} object for the provided bucket and blob names. Performs an RPC call to - * get the latest blob information. Returns {@code null} if the blob does not exist. - * - * @param storage the storage service used for issuing requests - * @param bucket bucket's name - * @param options blob get options - * @param blob blob's name - * @return the {@code Blob} object or {@code null} if not found - * @throws StorageException upon failure - */ - public static Blob get(Storage storage, String bucket, String blob, - Storage.BlobGetOption... options) { - return get(storage, BlobId.of(bucket, blob), options); - } + private final Storage storage; + private final BlobInfo.BuilderImpl infoBuilder; - /** - * Creates a {@code Blob} object for the provided {@code blobId}. Performs an RPC call to get the - * latest blob information. Returns {@code null} if the blob does not exist. - * - * @param storage the storage service used for issuing requests - * @param blobId blob's identifier - * @param options blob get options - * @return the {@code Blob} object or {@code null} if not found - * @throws StorageException upon failure - */ - public static Blob get(Storage storage, BlobId blobId, Storage.BlobGetOption... options) { - BlobInfo info = storage.get(blobId, options); - return info != null ? new Blob(storage, info) : null; - } + Builder(Blob blob) { + this.storage = blob.storage(); + this.infoBuilder = new BlobInfo.BuilderImpl(blob); + } - /** - * Returns the blob's information. - */ - public BlobInfo info() { - return info; + @Override + public Builder blobId(BlobId blobId) { + infoBuilder.blobId(blobId); + return this; + } + + @Override + Builder id(String id) { + infoBuilder.id(id); + return this; + } + + @Override + public Builder contentType(String contentType) { + infoBuilder.contentType(contentType); + return this; + } + + @Override + public Builder contentDisposition(String contentDisposition) { + infoBuilder.contentDisposition(contentDisposition); + return this; + } + + @Override + public Builder contentLanguage(String contentLanguage) { + infoBuilder.contentLanguage(contentLanguage); + return this; + } + + @Override + public Builder contentEncoding(String contentEncoding) { + infoBuilder.contentEncoding(contentEncoding); + return this; + } + + @Override + Builder componentCount(Integer componentCount) { + infoBuilder.componentCount(componentCount); + return this; + } + + @Override + public Builder cacheControl(String cacheControl) { + infoBuilder.cacheControl(cacheControl); + return this; + } + + @Override + public Builder acl(List acl) { + infoBuilder.acl(acl); + return this; + } + + @Override + Builder owner(Acl.Entity owner) { + infoBuilder.owner(owner); + return this; + } + + @Override + Builder size(Long size) { + infoBuilder.size(size); + return this; + } + + @Override + Builder etag(String etag) { + infoBuilder.etag(etag); + return this; + } + + @Override + Builder selfLink(String selfLink) { + infoBuilder.selfLink(selfLink); + return this; + } + + @Override + public Builder md5(String md5) { + infoBuilder.md5(md5); + return this; + } + + @Override + public Builder crc32c(String crc32c) { + infoBuilder.crc32c(crc32c); + return this; + } + + @Override + Builder mediaLink(String mediaLink) { + infoBuilder.mediaLink(mediaLink); + return this; + } + + @Override + public Builder metadata(Map metadata) { + infoBuilder.metadata(metadata); + return this; + } + + @Override + Builder metageneration(Long metageneration) { + infoBuilder.metageneration(metageneration); + return this; + } + + @Override + Builder deleteTime(Long deleteTime) { + infoBuilder.deleteTime(deleteTime); + return this; + } + + @Override + Builder updateTime(Long updateTime) { + infoBuilder.updateTime(updateTime); + return this; + } + + @Override + public Blob build() { + return new Blob(storage, infoBuilder); + } } - /** - * Returns the blob's id. - */ - public BlobId id() { - return info.blobId(); + Blob(Storage storage, BlobInfo.BuilderImpl infoBuilder) { + super(infoBuilder); + this.storage = checkNotNull(storage); + this.options = storage.options(); } /** @@ -211,9 +311,9 @@ public BlobId id() { */ public boolean exists(BlobSourceOption... options) { int length = options.length; - Storage.BlobGetOption[] getOptions = Arrays.copyOf(toGetOptions(info, options), length + 1); + Storage.BlobGetOption[] getOptions = Arrays.copyOf(toGetOptions(this, options), length + 1); getOptions[length] = Storage.BlobGetOption.fields(); - return storage.get(info.blobId(), getOptions) != null; + return storage.get(blobId(), getOptions) != null; } /** @@ -223,7 +323,7 @@ public boolean exists(BlobSourceOption... options) { * @throws StorageException upon failure */ public byte[] content(Storage.BlobSourceOption... options) { - return storage.readAllBytes(info.blobId(), options); + return storage.readAllBytes(blobId(), options); } /** @@ -234,7 +334,7 @@ public byte[] content(Storage.BlobSourceOption... options) { * @throws StorageException upon failure */ public Blob reload(BlobSourceOption... options) { - return Blob.get(storage, info.blobId(), toGetOptions(info, options)); + return storage.get(blobId(), toGetOptions(this, options)); } /** @@ -243,27 +343,24 @@ public Blob reload(BlobSourceOption... options) { * {@link #delete} operations. A new {@code Blob} object is returned. By default no checks are * 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())}. + * option: {@code newBlob.update(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}. + *

Original metadata are merged with metadata in the provided in this {@code blob}. To replace + * metadata instead you first have to unset them. Unsetting metadata can be done by setting this + * {@code blob}'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());}
+   * 
    {@code blob.toBuilder().metadata(null).build().update();}
+   *    {@code blob.toBuilder().metadata(newMetadata).build().update();}
    * 
* - * @param blobInfo new blob's information. Bucket and blob names must match the current ones * @param options update options * @return a {@code Blob} object with updated information * @throws StorageException upon failure */ - public Blob update(BlobInfo blobInfo, BlobTargetOption... options) { - checkArgument(Objects.equals(blobInfo.bucket(), info.bucket()), "Bucket name must match"); - checkArgument(Objects.equals(blobInfo.name(), info.name()), "Blob name must match"); - return new Blob(storage, storage.update(blobInfo, options)); + public Blob update(BlobTargetOption... options) { + return storage.update(this, options); } /** @@ -274,7 +371,7 @@ public Blob update(BlobInfo blobInfo, BlobTargetOption... options) { * @throws StorageException upon failure */ public boolean delete(BlobSourceOption... options) { - return storage.delete(info.blobId(), toSourceOptions(info, options)); + return storage.delete(blobId(), toSourceOptions(this, options)); } /** @@ -288,8 +385,11 @@ public boolean delete(BlobSourceOption... options) { * @throws StorageException upon failure */ public CopyWriter copyTo(BlobId targetBlob, BlobSourceOption... options) { - CopyRequest copyRequest = CopyRequest.builder().source(info.bucket(), info.name()) - .sourceOptions(toSourceOptions(info, options)).target(targetBlob).build(); + CopyRequest copyRequest = CopyRequest.builder() + .source(bucket(), name()) + .sourceOptions(toSourceOptions(this, options)) + .target(targetBlob) + .build(); return storage.copy(copyRequest); } @@ -304,7 +404,7 @@ public CopyWriter copyTo(BlobId targetBlob, BlobSourceOption... options) { * @throws StorageException upon failure */ public CopyWriter copyTo(String targetBucket, BlobSourceOption... options) { - return copyTo(targetBucket, info.name(), options); + return copyTo(targetBucket, name(), options); } /** @@ -329,7 +429,7 @@ public CopyWriter copyTo(String targetBucket, String targetBlob, BlobSourceOptio * @throws StorageException upon failure */ public ReadChannel reader(BlobSourceOption... options) { - return storage.reader(info.blobId(), toSourceOptions(info, options)); + return storage.reader(blobId(), toSourceOptions(this, options)); } /** @@ -341,7 +441,7 @@ public ReadChannel reader(BlobSourceOption... options) { * @throws StorageException upon failure */ public WriteChannel writer(BlobWriteOption... options) { - return storage.writer(info, options); + return storage.writer(this, options); } /** @@ -358,7 +458,7 @@ public WriteChannel writer(BlobWriteOption... options) { * @see Signed-URLs */ public URL signUrl(long duration, TimeUnit unit, SignUrlOption... options) { - return storage.signUrl(info, duration, unit, options); + return storage.signUrl(this, duration, unit, options); } /** @@ -368,97 +468,29 @@ public Storage storage() { return storage; } - /** - * Gets the requested blobs. A batch request is used to fetch blobs. - * - * @param storage the storage service used to issue the request - * @param first the first blob to get - * @param second the second blob to get - * @param other other 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(Storage storage, BlobId first, BlobId second, BlobId... other) { - checkNotNull(storage); - checkNotNull(first); - checkNotNull(second); - checkNotNull(other); - ImmutableList blobs = ImmutableList.builder() - .add(first) - .add(second) - .addAll(Arrays.asList(other)) - .build(); - return get(storage, blobs); + @Override + public Builder toBuilder() { + return new Builder(this); } - /** - * Gets the requested blobs. A batch request is used to fetch blobs. - * - * @param storage the storage service used to issue the request - * @param blobs list of 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, List blobs) { - checkNotNull(storage); - checkNotNull(blobs); - BlobId[] blobArray = blobs.toArray(new BlobId[blobs.size()]); - return Collections.unmodifiableList(Lists.transform(storage.get(blobArray), - new Function() { - @Override - public Blob apply(BlobInfo blobInfo) { - return blobInfo != null ? new Blob(storage, blobInfo) : null; - } - })); + @Override + public boolean equals(Object obj) { + return obj instanceof Blob && Objects.equals(toPb(), ((Blob) obj).toPb()) + && Objects.equals(options, ((Blob) obj).options); } - /** - * 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 - * @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 blobInfo) { - return blobInfo != null ? new Blob(storage, blobInfo) : null; - } - })); + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), options); } - /** - * 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 - * @return an immutable list of booleans. If a blob has been deleted the corresponding item in the - * list is {@code true}. If a blob was not found, deletion failed or access to the resource - * was denied the corresponding item is {@code false} - * @throws StorageException upon failure - */ - public static List delete(Storage storage, BlobId... blobs) { - checkNotNull(storage); - checkNotNull(blobs); - if (blobs.length == 0) { - return Collections.emptyList(); - } - return storage.delete(blobs); + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + this.storage = options.service(); + } + + static Blob fromPb(Storage storage, StorageObject storageObject) { + BlobInfo info = BlobInfo.fromPb(storageObject); + return new Blob(storage, new BlobInfo.BuilderImpl(info)); } } 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 b27d00d68a16..54fabe87d766 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 @@ -47,22 +47,16 @@ * @see Concepts and * Terminology */ -public final class BlobInfo implements Serializable { +public class BlobInfo implements Serializable { - static final Function FROM_PB_FUNCTION = - new Function() { - @Override - public BlobInfo apply(StorageObject pb) { - return BlobInfo.fromPb(pb); - } - }; - static final Function TO_PB_FUNCTION = + static final Function INFO_TO_PB_FUNCTION = new Function() { @Override public StorageObject apply(BlobInfo blobInfo) { return blobInfo.toPb(); } }; + private static final long serialVersionUID = 2228487739943277159L; private final BlobId blobId; private final String id; @@ -96,7 +90,110 @@ public Set> entrySet() { } } - public static final class Builder { + /** + * Builder for {@code BlobInfo}. + */ + public abstract static class Builder { + + /** + * Sets the blob identity. + */ + public abstract Builder blobId(BlobId blobId); + + abstract Builder id(String id); + + /** + * Sets the blob's data content type. + * + * @see Content-Type + */ + public abstract Builder contentType(String contentType); + + /** + * Sets the blob's data content disposition. + * + * @see Content-Disposition + */ + public abstract Builder contentDisposition(String contentDisposition); + + /** + * Sets the blob's data content language. + * + * @see Content-Language + */ + public abstract Builder contentLanguage(String contentLanguage); + + /** + * Sets the blob's data content encoding. + * + * @see Content-Encoding + */ + public abstract Builder contentEncoding(String contentEncoding); + + abstract Builder componentCount(Integer componentCount); + + /** + * Sets the blob's data cache control. + * + * @see Cache-Control + */ + public abstract Builder cacheControl(String cacheControl); + + /** + * Sets the blob's access control configuration. + * + * @see + * About Access Control Lists + */ + public abstract Builder acl(List acl); + + abstract Builder owner(Acl.Entity owner); + + abstract Builder size(Long size); + + abstract Builder etag(String etag); + + abstract Builder selfLink(String selfLink); + + /** + * Sets the MD5 hash of blob's data. MD5 value must be encoded in base64. + * + * @see + * Hashes and ETags: Best Practices + */ + public abstract Builder md5(String md5); + + /** + * Sets the CRC32C checksum of blob's data as described in + * RFC 4960, Appendix B; encoded in + * base64 in big-endian order. + * + * @see + * Hashes and ETags: Best Practices + */ + public abstract Builder crc32c(String crc32c); + + abstract Builder mediaLink(String mediaLink); + + /** + * Sets the blob's user provided metadata. + */ + public abstract Builder metadata(Map metadata); + + abstract Builder metageneration(Long metageneration); + + abstract Builder deleteTime(Long deleteTime); + + abstract Builder updateTime(Long updateTime); + + /** + * Creates a {@code BlobInfo} object. + */ + public abstract BlobInfo build(); + } + + static final class BuilderImpl extends Builder { private BlobId blobId; private String id; @@ -119,9 +216,11 @@ public static final class Builder { private Long deleteTime; private Long updateTime; - private Builder() {} + BuilderImpl(BlobId blobId) { + this.blobId = blobId; + } - private Builder(BlobInfo blobInfo) { + BuilderImpl(BlobInfo blobInfo) { blobId = blobInfo.blobId; id = blobInfo.id; cacheControl = blobInfo.cacheControl; @@ -144,168 +243,135 @@ private Builder(BlobInfo blobInfo) { updateTime = blobInfo.updateTime; } - /** - * Sets the blob identity. - */ + @Override public Builder blobId(BlobId blobId) { this.blobId = checkNotNull(blobId); return this; } + @Override Builder id(String id) { this.id = id; return this; } - /** - * Sets the blob's data content type. - * - * @see Content-Type - */ + @Override public Builder contentType(String contentType) { this.contentType = firstNonNull(contentType, Data.nullOf(String.class)); return this; } - /** - * Sets the blob's data content disposition. - * - * @see Content-Disposition - */ + @Override public Builder contentDisposition(String contentDisposition) { this.contentDisposition = firstNonNull(contentDisposition, Data.nullOf(String.class)); return this; } - /** - * Sets the blob's data content language. - * - * @see Content-Language - */ + @Override public Builder contentLanguage(String contentLanguage) { this.contentLanguage = firstNonNull(contentLanguage, Data.nullOf(String.class)); return this; } - /** - * Sets the blob's data content encoding. - * - * @see Content-Encoding - */ + @Override public Builder contentEncoding(String contentEncoding) { this.contentEncoding = firstNonNull(contentEncoding, Data.nullOf(String.class)); return this; } + @Override Builder componentCount(Integer componentCount) { this.componentCount = componentCount; return this; } - /** - * Sets the blob's data cache control. - * - * @see Cache-Control - */ + @Override public Builder cacheControl(String cacheControl) { this.cacheControl = firstNonNull(cacheControl, Data.nullOf(String.class)); return this; } - /** - * Sets the blob's access control configuration. - * - * @see - * About Access Control Lists - */ + @Override public Builder acl(List acl) { this.acl = acl != null ? ImmutableList.copyOf(acl) : null; return this; } + @Override Builder owner(Acl.Entity owner) { this.owner = owner; return this; } + @Override Builder size(Long size) { this.size = size; return this; } + @Override Builder etag(String etag) { this.etag = etag; return this; } + @Override Builder selfLink(String selfLink) { this.selfLink = selfLink; return this; } - /** - * Sets the MD5 hash of blob's data. MD5 value must be encoded in base64. - * - * @see - * Hashes and ETags: Best Practices - */ + @Override public Builder md5(String md5) { this.md5 = firstNonNull(md5, Data.nullOf(String.class)); return this; } - /** - * Sets the CRC32C checksum of blob's data as described in - * RFC 4960, Appendix B; encoded in - * base64 in big-endian order. - * - * @see - * Hashes and ETags: Best Practices - */ + @Override public Builder crc32c(String crc32c) { this.crc32c = firstNonNull(crc32c, Data.nullOf(String.class)); return this; } + @Override Builder mediaLink(String mediaLink) { this.mediaLink = mediaLink; return this; } - /** - * Sets the blob's user provided metadata. - */ + @Override public Builder metadata(Map metadata) { this.metadata = metadata != null ? new HashMap<>(metadata) : Data.>nullOf(ImmutableEmptyMap.class); return this; } + @Override Builder metageneration(Long metageneration) { this.metageneration = metageneration; return this; } + @Override Builder deleteTime(Long deleteTime) { this.deleteTime = deleteTime; return this; } + @Override Builder updateTime(Long updateTime) { this.updateTime = updateTime; return this; } - /** - * Creates a {@code BlobInfo} object. - */ + @Override public BlobInfo build() { checkNotNull(blobId); return new BlobInfo(this); } } - private BlobInfo(Builder builder) { + BlobInfo(BuilderImpl builder) { blobId = builder.blobId; id = builder.id; cacheControl = builder.cacheControl; @@ -526,7 +592,7 @@ public Long updateTime() { * Returns a builder for the current blob. */ public Builder toBuilder() { - return new Builder(this); + return new BuilderImpl(this); } @Override @@ -548,7 +614,8 @@ public int hashCode() { @Override public boolean equals(Object obj) { - return obj instanceof BlobInfo && Objects.equals(toPb(), ((BlobInfo) obj).toPb()); + return obj != null && obj.getClass().equals(BlobInfo.class) + && Objects.equals(toPb(), ((BlobInfo) obj).toPb()); } StorageObject toPb() { @@ -609,7 +676,7 @@ public static Builder builder(BucketInfo bucketInfo, String name) { * Returns a {@code BlobInfo} builder where blob identity is set using the provided values. */ public static Builder builder(String bucket, String name) { - return new Builder().blobId(BlobId.of(bucket, name)); + return builder(BlobId.of(bucket, name)); } /** @@ -623,11 +690,11 @@ public static Builder builder(BucketInfo bucketInfo, String name, Long generatio * Returns a {@code BlobInfo} builder where blob identity is set using the provided values. */ public static Builder builder(String bucket, String name, Long generation) { - return new Builder().blobId(BlobId.of(bucket, name, generation)); + return builder(BlobId.of(bucket, name, generation)); } public static Builder builder(BlobId blobId) { - return new Builder().blobId(blobId); + return new BuilderImpl(blobId); } static BlobInfo fromPb(StorageObject storageObject) { 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 3acd3f5d79b9..c438f497730e 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,16 +16,12 @@ 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.Bucket.BucketSourceOption.toGetOptions; import static com.google.gcloud.storage.Bucket.BucketSourceOption.toSourceOptions; -import com.google.common.base.Function; import com.google.common.base.MoreObjects; -import com.google.common.collect.Iterators; import com.google.gcloud.Page; -import com.google.gcloud.PageImpl; import com.google.gcloud.spi.StorageRpc; import com.google.gcloud.storage.Storage.BlobGetOption; import com.google.gcloud.storage.Storage.BlobTargetOption; @@ -35,11 +31,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; -import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Objects; @@ -48,78 +42,16 @@ * *

Objects of this class are immutable. Operations that modify the bucket like {@link #update} * return a new object. To get a {@code Bucket} object with the most recent information use - * {@link #reload}. + * {@link #reload}. {@code Bucket} adds a layer of service-related functionality over + * {@link BucketInfo}. *

*/ -public final class Bucket { +public final class Bucket extends BucketInfo { - private final Storage storage; - private final BucketInfo info; + private static final long serialVersionUID = 8574601739542252586L; - private static class BlobPageFetcher implements PageImpl.NextPageFetcher { - - private static final long serialVersionUID = 3221100177471323801L; - - private final StorageOptions options; - private final Page infoPage; - - BlobPageFetcher(StorageOptions options, Page infoPage) { - this.options = options; - this.infoPage = infoPage; - } - - @Override - public Page nextPage() { - Page nextInfoPage = infoPage.nextPage(); - return new PageImpl<>(new BlobPageFetcher(options, nextInfoPage), - nextInfoPage.nextPageCursor(), new LazyBlobIterable(options, nextInfoPage.values())); - } - } - - private static class LazyBlobIterable implements Iterable, Serializable { - - private static final long serialVersionUID = -3092290247725378832L; - - private final StorageOptions options; - private final Iterable infoIterable; - private transient Storage storage; - - public LazyBlobIterable(StorageOptions options, Iterable infoIterable) { - this.options = options; - this.infoIterable = infoIterable; - this.storage = options.service(); - } - - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - this.storage = options.service(); - } - - @Override - public Iterator iterator() { - return Iterators.transform(infoIterable.iterator(), new Function() { - @Override - public Blob apply(BlobInfo blobInfo) { - return new Blob(storage, blobInfo); - } - }); - } - - @Override - public int hashCode() { - return Objects.hash(options, infoIterable); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof LazyBlobIterable)) { - return false; - } - LazyBlobIterable other = (LazyBlobIterable) obj; - return Objects.equals(options, other.options) - && Objects.equals(infoIterable, other.infoIterable); - } - } + private final StorageOptions options; + private transient Storage storage; /** * Class for specifying bucket source options when {@code Bucket} methods are used. @@ -193,37 +125,123 @@ static Storage.BucketGetOption[] toGetOptions(BucketInfo bucketInfo, } /** - * Constructs 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 + * Builder for {@code Bucket}. */ - public Bucket(Storage storage, BucketInfo info) { - this.storage = checkNotNull(storage); - this.info = checkNotNull(info); - } + public static class Builder extends BucketInfo.Builder { + private final Storage storage; + private final BucketInfo.BuilderImpl infoBuilder; - /** - * Creates a {@code Bucket} object for the provided bucket name. Performs an RPC call to get the - * latest bucket information. - * - * @param storage the storage service used for issuing requests - * @param bucket bucket's name - * @param options blob get options - * @return the {@code Bucket} object or {@code null} if not found - * @throws StorageException upon failure - */ - public static Bucket get(Storage storage, String bucket, Storage.BucketGetOption... options) { - BucketInfo info = storage.get(bucket, options); - return info != null ? new Bucket(storage, info) : null; + Builder(Bucket bucket) { + this.storage = bucket.storage; + this.infoBuilder = new BucketInfo.BuilderImpl(bucket); + } + + @Override + public Builder name(String name) { + infoBuilder.name(name); + return this; + } + + @Override + Builder id(String id) { + infoBuilder.id(id); + return this; + } + + @Override + Builder owner(Acl.Entity owner) { + infoBuilder.owner(owner); + return this; + } + + @Override + Builder selfLink(String selfLink) { + infoBuilder.selfLink(selfLink); + return this; + } + + @Override + public Builder versioningEnabled(Boolean enable) { + infoBuilder.versioningEnabled(enable); + return this; + } + + @Override + public Builder indexPage(String indexPage) { + infoBuilder.indexPage(indexPage); + return this; + } + + @Override + public Builder notFoundPage(String notFoundPage) { + infoBuilder.notFoundPage(notFoundPage); + return this; + } + + @Override + public Builder deleteRules(Iterable rules) { + infoBuilder.deleteRules(rules); + return this; + } + + @Override + public Builder storageClass(String storageClass) { + infoBuilder.storageClass(storageClass); + return this; + } + + @Override + public Builder location(String location) { + infoBuilder.location(location); + return this; + } + + @Override + Builder etag(String etag) { + infoBuilder.etag(etag); + return this; + } + + @Override + Builder createTime(Long createTime) { + infoBuilder.createTime(createTime); + return this; + } + + @Override + Builder metageneration(Long metageneration) { + infoBuilder.metageneration(metageneration); + return this; + } + + @Override + public Builder cors(Iterable cors) { + infoBuilder.cors(cors); + return this; + } + + @Override + public Builder acl(Iterable acl) { + infoBuilder.acl(acl); + return this; + } + + @Override + public Builder defaultAcl(Iterable acl) { + infoBuilder.defaultAcl(acl); + return this; + } + + @Override + public Bucket build() { + return new Bucket(storage, infoBuilder); + } } - /** - * Returns the bucket's information. - */ - public BucketInfo info() { - return info; + Bucket(Storage storage, BucketInfo.BuilderImpl infoBuilder) { + super(infoBuilder); + this.storage = checkNotNull(storage); + this.options = storage.options(); } /** @@ -234,9 +252,9 @@ public BucketInfo info() { */ public boolean exists(BucketSourceOption... options) { int length = options.length; - Storage.BucketGetOption[] getOptions = Arrays.copyOf(toGetOptions(info, options), length + 1); + Storage.BucketGetOption[] getOptions = Arrays.copyOf(toGetOptions(this, options), length + 1); getOptions[length] = Storage.BucketGetOption.fields(); - return storage.get(info.name(), getOptions) != null; + return storage.get(name(), getOptions) != null; } /** @@ -247,7 +265,7 @@ public boolean exists(BucketSourceOption... options) { * @throws StorageException upon failure */ public Bucket reload(BucketSourceOption... options) { - return Bucket.get(storage, info.name(), toGetOptions(info, options)); + return storage.get(name(), toGetOptions(this, options)); } /** @@ -255,16 +273,14 @@ public Bucket reload(BucketSourceOption... options) { * is returned. By default no checks are made on the metadata generation of the current bucket. * If you want to update the information only if the current bucket metadata are at their latest * version use the {@code metagenerationMatch} option: - * {@code bucket.update(newInfo, BucketTargetOption.metagenerationMatch())} + * {@code bucket.update(BucketTargetOption.metagenerationMatch())} * - * @param bucketInfo new bucket's information. Name must match the one of the current bucket * @param options update options * @return a {@code Bucket} object with updated information * @throws StorageException upon failure */ - public Bucket update(BucketInfo bucketInfo, BucketTargetOption... options) { - checkArgument(Objects.equals(bucketInfo.name(), info.name()), "Bucket name must match"); - return new Bucket(storage, storage.update(bucketInfo, options)); + public Bucket update(BucketTargetOption... options) { + return storage.update(this, options); } /** @@ -275,36 +291,33 @@ public Bucket update(BucketInfo bucketInfo, BucketTargetOption... options) { * @throws StorageException upon failure */ public boolean delete(BucketSourceOption... options) { - return storage.delete(info.name(), toSourceOptions(info, options)); + return storage.delete(name(), toSourceOptions(this, options)); } /** * Returns the paginated list of {@code Blob} in this bucket. - * + * * @param options options for listing blobs * @throws StorageException upon failure */ public Page list(Storage.BlobListOption... options) { - Page infoPage = storage.list(info.name(), options); - StorageOptions storageOptions = storage.options(); - return new PageImpl<>(new BlobPageFetcher(storageOptions, infoPage), infoPage.nextPageCursor(), - new LazyBlobIterable(storageOptions, infoPage.values())); + return storage.list(name(), options); } /** * Returns the requested blob in this bucket or {@code null} if not found. - * + * * @param blob name of the requested blob * @param options blob search options * @throws StorageException upon failure */ public Blob get(String blob, BlobGetOption... options) { - return new Blob(storage, storage.get(BlobId.of(info.name(), blob), options)); + return storage.get(BlobId.of(name(), blob), options); } /** * Returns a list of requested blobs in this bucket. Blobs that do not exist are null. - * + * * @param blobName1 first blob to get * @param blobName2 second blob to get * @param blobNames other blobs to get @@ -313,16 +326,16 @@ public Blob get(String blob, BlobGetOption... options) { */ public List get(String blobName1, String blobName2, String... blobNames) { BatchRequest.Builder batch = BatchRequest.builder(); - batch.get(info.name(), blobName1); - batch.get(info.name(), blobName2); + batch.get(name(), blobName1); + batch.get(name(), blobName2); for (String name : blobNames) { - batch.get(info.name(), name); + batch.get(name(), name); } List blobs = new ArrayList<>(blobNames.length); BatchResponse response = storage.submit(batch.build()); - for (BatchResponse.Result result : response.gets()) { + for (BatchResponse.Result result : response.gets()) { BlobInfo blobInfo = result.get(); - blobs.add(blobInfo != null ? new Blob(storage, blobInfo) : null); + blobs.add(blobInfo != null ? new Blob(storage, new BlobInfo.BuilderImpl(blobInfo)) : null); } return Collections.unmodifiableList(blobs); } @@ -332,7 +345,7 @@ public List get(String blobName1, String blobName2, String... blobNames) { * For large content, {@link Blob#writer(com.google.gcloud.storage.Storage.BlobWriteOption...)} * is recommended as it uses resumable upload. MD5 and CRC32C hashes of {@code content} are * computed and used for validating transferred data. - * + * * @param blob a blob name * @param content the blob content * @param contentType the blob content type. If {@code null} then @@ -342,16 +355,16 @@ public List get(String blobName1, String blobName2, String... blobNames) { * @throws StorageException upon failure */ public Blob create(String blob, byte[] content, String contentType, BlobTargetOption... options) { - BlobInfo blobInfo = BlobInfo.builder(BlobId.of(info.name(), blob)) + BlobInfo blobInfo = BlobInfo.builder(BlobId.of(name(), blob)) .contentType(MoreObjects.firstNonNull(contentType, Storage.DEFAULT_CONTENT_TYPE)).build(); - return new Blob(storage, storage.create(blobInfo, content, options)); + return storage.create(blobInfo, content, options); } /** * Creates a new blob in this bucket. Direct upload is used to upload {@code content}. * For large content, {@link Blob#writer(com.google.gcloud.storage.Storage.BlobWriteOption...)} * is recommended as it uses resumable upload. - * + * * @param blob a blob name * @param content the blob content as a stream * @param contentType the blob content type. If {@code null} then @@ -362,9 +375,9 @@ public Blob create(String blob, byte[] content, String contentType, BlobTargetOp */ public Blob create(String blob, InputStream content, String contentType, BlobWriteOption... options) { - BlobInfo blobInfo = BlobInfo.builder(BlobId.of(info.name(), blob)) + BlobInfo blobInfo = BlobInfo.builder(BlobId.of(name(), blob)) .contentType(MoreObjects.firstNonNull(contentType, Storage.DEFAULT_CONTENT_TYPE)).build(); - return new Blob(storage, storage.create(blobInfo, content, options)); + return storage.create(blobInfo, content, options); } /** @@ -373,4 +386,29 @@ public Blob create(String blob, InputStream content, String contentType, public Storage storage() { return storage; } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Bucket && Objects.equals(toPb(), ((Bucket) obj).toPb()) + && Objects.equals(options, ((Bucket) obj).options); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), options); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + this.storage = options.service(); + } + + static Bucket fromPb(Storage storage, com.google.api.services.storage.model.Bucket bucketPb) { + return new Bucket(storage, new BucketInfo.BuilderImpl(BucketInfo.fromPb(bucketPb))); + } } 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 62fbf9c6521f..bf34413f417f 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 @@ -48,7 +48,7 @@ * @see Concepts and * Terminology */ -public final class BucketInfo implements Serializable { +public class BucketInfo implements Serializable { static final Function FROM_PB_FUNCTION = new Function() { @@ -317,7 +317,99 @@ void populateCondition(Rule.Condition condition) { } } - public static final class Builder { + /** + * Builder for {@code BucketInfo}. + */ + public abstract static class Builder { + /** + * Sets the bucket's name. + */ + public abstract Builder name(String name); + + abstract Builder id(String id); + + abstract Builder owner(Acl.Entity owner); + + abstract Builder selfLink(String selfLink); + + /** + * Sets whether versioning should be enabled for this bucket. When set to true, versioning is + * fully enabled. + */ + public abstract Builder versioningEnabled(Boolean enable); + + /** + * Sets the bucket's website index page. Behaves as the bucket's directory index where missing + * blobs are treated as potential directories. + */ + public abstract Builder indexPage(String indexPage); + + /** + * Sets the custom object to return when a requested resource is not found. + */ + public abstract Builder notFoundPage(String notFoundPage); + + /** + * Sets the bucket's lifecycle configuration as a number of delete rules. + * + * @see Lifecycle Management + */ + public abstract Builder deleteRules(Iterable rules); + + /** + * Sets the bucket's storage class. This defines how blobs in the bucket are stored and + * determines the SLA and the cost of storage. A list of supported values is available + * here. + */ + public abstract Builder storageClass(String storageClass); + + /** + * Sets the bucket's location. Data for blobs in the bucket resides in physical storage within + * this region. A list of supported values is available + * here. + */ + public abstract Builder location(String location); + + abstract Builder etag(String etag); + + abstract Builder createTime(Long createTime); + + abstract Builder metageneration(Long metageneration); + + /** + * Sets the bucket's Cross-Origin Resource Sharing (CORS) configuration. + * + * @see + * Cross-Origin Resource Sharing (CORS) + */ + public abstract Builder cors(Iterable cors); + + /** + * Sets the bucket's access control configuration. + * + * @see + * About Access Control Lists + */ + public abstract Builder acl(Iterable acl); + + /** + * Sets the default access control configuration to apply to bucket's blobs when no other + * configuration is specified. + * + * @see + * About Access Control Lists + */ + public abstract Builder defaultAcl(Iterable acl); + + /** + * Creates a {@code BucketInfo} object. + */ + public abstract BucketInfo build(); + } + + static final class BuilderImpl extends Builder { private String id; private String name; @@ -336,9 +428,11 @@ public static final class Builder { private List acl; private List defaultAcl; - private Builder() {} + BuilderImpl(String name) { + this.name = name; + } - private Builder(BucketInfo bucketInfo) { + BuilderImpl(BucketInfo bucketInfo) { id = bucketInfo.id; name = bucketInfo.name; etag = bucketInfo.etag; @@ -357,144 +451,110 @@ private Builder(BucketInfo bucketInfo) { deleteRules = bucketInfo.deleteRules; } - /** - * Sets the bucket's name. - */ + @Override public Builder name(String name) { this.name = checkNotNull(name); return this; } + @Override Builder id(String id) { this.id = id; return this; } + @Override Builder owner(Acl.Entity owner) { this.owner = owner; return this; } + @Override Builder selfLink(String selfLink) { this.selfLink = selfLink; return this; } - /** - * Sets whether versioning should be enabled for this bucket. When set to true, versioning is - * fully enabled. - */ + @Override public Builder versioningEnabled(Boolean enable) { this.versioningEnabled = firstNonNull(enable, Data.nullOf(Boolean.class)); return this; } - /** - * Sets the bucket's website index page. Behaves as the bucket's directory index where missing - * blobs are treated as potential directories. - */ + @Override public Builder indexPage(String indexPage) { this.indexPage = indexPage; return this; } - /** - * Sets the custom object to return when a requested resource is not found. - */ + @Override public Builder notFoundPage(String notFoundPage) { this.notFoundPage = notFoundPage; return this; } - /** - * Sets the bucket's lifecycle configuration as a number of delete rules. - * - * @see Lifecycle Management - */ + @Override public Builder deleteRules(Iterable rules) { this.deleteRules = rules != null ? ImmutableList.copyOf(rules) : null; return this; } - /** - * Sets the bucket's storage class. This defines how blobs in the bucket are stored and - * determines the SLA and the cost of storage. A list of supported values is available - * here. - */ + @Override public Builder storageClass(String storageClass) { this.storageClass = storageClass; return this; } - /** - * Sets the bucket's location. Data for blobs in the bucket resides in physical storage within - * this region. A list of supported values is available - * here. - */ + @Override public Builder location(String location) { this.location = location; return this; } + @Override Builder etag(String etag) { this.etag = etag; return this; } + @Override Builder createTime(Long createTime) { this.createTime = createTime; return this; } + @Override Builder metageneration(Long metageneration) { this.metageneration = metageneration; return this; } - /** - * Sets the bucket's Cross-Origin Resource Sharing (CORS) configuration. - * - * @see - * Cross-Origin Resource Sharing (CORS) - */ + @Override public Builder cors(Iterable cors) { this.cors = cors != null ? ImmutableList.copyOf(cors) : null; return this; } - /** - * Sets the bucket's access control configuration. - * - * @see - * About Access Control Lists - */ + @Override public Builder acl(Iterable acl) { this.acl = acl != null ? ImmutableList.copyOf(acl) : null; return this; } - /** - * Sets the default access control configuration to apply to bucket's blobs when no other - * configuration is specified. - * - * @see - * About Access Control Lists - */ + @Override public Builder defaultAcl(Iterable acl) { this.defaultAcl = acl != null ? ImmutableList.copyOf(acl) : null; return this; } - /** - * Creates a {@code BucketInfo} object. - */ + @Override public BucketInfo build() { checkNotNull(name); return new BucketInfo(this); } } - private BucketInfo(Builder builder) { + BucketInfo(BuilderImpl builder) { id = builder.id; name = builder.name; etag = builder.etag; @@ -649,7 +709,7 @@ public List defaultAcl() { * Returns a builder for the current bucket. */ public Builder toBuilder() { - return new Builder(this); + return new BuilderImpl(this); } @Override @@ -659,7 +719,8 @@ public int hashCode() { @Override public boolean equals(Object obj) { - return obj instanceof BucketInfo && Objects.equals(toPb(), ((BucketInfo) obj).toPb()); + return obj != null && obj.getClass().equals(BucketInfo.class) + && Objects.equals(toPb(), ((BucketInfo) obj).toPb()); } @Override @@ -743,11 +804,11 @@ public static BucketInfo of(String name) { * Returns a {@code BucketInfo} builder where the bucket's name is set to the provided name. */ public static Builder builder(String name) { - return new Builder().name(name); + return new BuilderImpl(name); } static BucketInfo fromPb(com.google.api.services.storage.model.Bucket bucketPb) { - Builder builder = new Builder().name(bucketPb.getName()); + Builder builder = new BuilderImpl(bucketPb.getName()); if (bucketPb.getId() != null) { builder.id(bucketPb.getId()); } 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 b550015d0516..d1799daede3e 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 @@ -1208,29 +1208,29 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx /** * Create a new bucket. * - * @return a complete bucket information + * @return a complete bucket * @throws StorageException upon failure */ - BucketInfo create(BucketInfo bucketInfo, BucketTargetOption... options); + Bucket create(BucketInfo bucketInfo, BucketTargetOption... options); /** * Create a new blob with no content. * - * @return a complete blob information + * @return a [@code Blob} with complete information * @throws StorageException upon failure */ - BlobInfo create(BlobInfo blobInfo, BlobTargetOption... options); + Blob create(BlobInfo blobInfo, BlobTargetOption... options); /** * Create a new blob. Direct upload is used to upload {@code content}. For large content, * {@link #writer} is recommended as it uses resumable upload. MD5 and CRC32C hashes of * {@code content} are computed and used for validating transferred data. * - * @return a complete blob information + * @return a [@code Blob} with complete information * @throws StorageException upon failure * @see Hashes and ETags */ - BlobInfo create(BlobInfo blobInfo, byte[] content, BlobTargetOption... options); + Blob create(BlobInfo blobInfo, byte[] content, BlobTargetOption... options); /** * Create a new blob. Direct upload is used to upload {@code content}. For large content, @@ -1239,52 +1239,52 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx * {@code BlobWriteOption.md5Match} and {@code BlobWriteOption.crc32cMatch} options. The given * input stream is closed upon success. * - * @return a complete blob information + * @return a [@code Blob} with complete information * @throws StorageException upon failure */ - BlobInfo create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options); + Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options); /** * Return the requested bucket or {@code null} if not found. * * @throws StorageException upon failure */ - BucketInfo get(String bucket, BucketGetOption... options); + Bucket get(String bucket, BucketGetOption... options); /** * Return the requested blob or {@code null} if not found. * * @throws StorageException upon failure */ - BlobInfo get(String bucket, String blob, BlobGetOption... options); + Blob get(String bucket, String blob, BlobGetOption... options); /** * Return the requested blob or {@code null} if not found. * * @throws StorageException upon failure */ - BlobInfo get(BlobId blob, BlobGetOption... options); + Blob get(BlobId blob, BlobGetOption... options); /** * Return the requested blob or {@code null} if not found. * * @throws StorageException upon failure */ - BlobInfo get(BlobId blob); + Blob get(BlobId blob); /** * List the project's buckets. * * @throws StorageException upon failure */ - Page list(BucketListOption... options); + Page list(BucketListOption... options); /** * List the bucket's blobs. * * @throws StorageException upon failure */ - Page list(String bucket, BlobListOption... options); + Page list(String bucket, BlobListOption... options); /** * Update bucket information. @@ -1292,7 +1292,7 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx * @return the updated bucket * @throws StorageException upon failure */ - BucketInfo update(BucketInfo bucketInfo, BucketTargetOption... options); + Bucket update(BucketInfo bucketInfo, BucketTargetOption... options); /** * Update blob information. Original metadata are merged with metadata in the provided @@ -1307,7 +1307,7 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx * @return the updated blob * @throws StorageException upon failure */ - BlobInfo update(BlobInfo blobInfo, BlobTargetOption... options); + Blob update(BlobInfo blobInfo, BlobTargetOption... options); /** * Update blob information. Original metadata are merged with metadata in the provided @@ -1322,7 +1322,7 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx * @return the updated blob * @throws StorageException upon failure */ - BlobInfo update(BlobInfo blobInfo); + Blob update(BlobInfo blobInfo); /** * Delete the requested bucket. @@ -1362,7 +1362,7 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx * @return the composed blob * @throws StorageException upon failure */ - BlobInfo compose(ComposeRequest composeRequest); + Blob compose(ComposeRequest composeRequest); /** * Sends a copy request. Returns a {@link CopyWriter} object for the provided @@ -1467,7 +1467,7 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx * }
* * @param blobInfo the blob associated with the signed URL - * @param duration time until the signed URL expires, expressed in {@code unit}. The finer + * @param duration time until the signed URL expires, expressed in {@code unit}. The finest * granularity supported is 1 second, finer granularities will be truncated * @param unit time unit of the {@code duration} parameter * @param options optional URL signing options @@ -1479,11 +1479,11 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx * Gets the requested blobs. A batch request is used to perform this call. * * @param blobIds blobs to get - * @return an immutable list of {@code BlobInfo} objects. If a blob does not exist or access to it + * @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 */ - List get(BlobId... blobIds); + List get(BlobId... blobIds); /** * Updates the requested blobs. A batch request is used to perform this call. Original metadata @@ -1493,11 +1493,11 @@ private static void checkContentType(BlobInfo blobInfo) throws IllegalArgumentEx * {@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 + * @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 */ - List update(BlobInfo... blobInfos); + List update(BlobInfo... blobInfos); /** * Deletes the requested blobs. A batch request is used to perform this call. 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 b6a833f26ab4..de77cba021a1 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 @@ -33,7 +33,6 @@ import com.google.api.services.storage.model.StorageObject; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.common.base.Function; -import com.google.common.base.Functions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -78,6 +77,14 @@ final class StorageImpl extends BaseService implements Storage { private static final String EMPTY_BYTE_ARRAY_MD5 = "1B2M2Y8AsgTpgAmY7PhCfg=="; private static final String EMPTY_BYTE_ARRAY_CRC32C = "AAAAAA=="; + private static final Function, Boolean> DELETE_FUNCTION = + new Function, Boolean>() { + @Override + public Boolean apply(Tuple tuple) { + return tuple.y(); + } + }; + private final StorageRpc storageRpc; StorageImpl(StorageOptions options) { @@ -86,11 +93,11 @@ final class StorageImpl extends BaseService implements Storage { } @Override - public BucketInfo create(BucketInfo bucketInfo, BucketTargetOption... options) { + public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) { final com.google.api.services.storage.model.Bucket bucketPb = bucketInfo.toPb(); final Map optionsMap = optionMap(bucketInfo, options); try { - return BucketInfo.fromPb(runWithRetries( + return Bucket.fromPb(this, runWithRetries( new Callable() { @Override public com.google.api.services.storage.model.Bucket call() { @@ -103,7 +110,7 @@ public com.google.api.services.storage.model.Bucket call() { } @Override - public BlobInfo create(BlobInfo blobInfo, BlobTargetOption... options) { + public Blob create(BlobInfo blobInfo, BlobTargetOption... options) { BlobInfo updatedInfo = blobInfo.toBuilder() .md5(EMPTY_BYTE_ARRAY_MD5) .crc32c(EMPTY_BYTE_ARRAY_CRC32C) @@ -112,7 +119,7 @@ public BlobInfo create(BlobInfo blobInfo, BlobTargetOption... options) { } @Override - public BlobInfo create(BlobInfo blobInfo, byte[] content, BlobTargetOption... options) { + public Blob create(BlobInfo blobInfo, byte[] content, BlobTargetOption... options) { content = firstNonNull(content, EMPTY_BYTE_ARRAY); BlobInfo updatedInfo = blobInfo.toBuilder() .md5(BaseEncoding.base64().encode(Hashing.md5().hashBytes(content).asBytes())) @@ -123,16 +130,16 @@ public BlobInfo create(BlobInfo blobInfo, byte[] content, BlobTargetOption... op } @Override - public BlobInfo create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) { + public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) { Tuple targetOptions = BlobTargetOption.convert(blobInfo, options); return create(targetOptions.x(), content, targetOptions.y()); } - private BlobInfo create(BlobInfo info, final InputStream content, BlobTargetOption... options) { + private Blob create(BlobInfo info, final InputStream content, BlobTargetOption... options) { final StorageObject blobPb = info.toPb(); final Map optionsMap = optionMap(info, options); try { - return BlobInfo.fromPb(runWithRetries(new Callable() { + return Blob.fromPb(this, runWithRetries(new Callable() { @Override public StorageObject call() { return storageRpc.create(blobPb, @@ -145,7 +152,7 @@ public StorageObject call() { } @Override - public BucketInfo get(String bucket, BucketGetOption... options) { + public Bucket get(String bucket, BucketGetOption... options) { final com.google.api.services.storage.model.Bucket bucketPb = BucketInfo.of(bucket).toPb(); final Map optionsMap = optionMap(options); try { @@ -156,19 +163,19 @@ public com.google.api.services.storage.model.Bucket call() { return storageRpc.get(bucketPb, optionsMap); } }, options().retryParams(), EXCEPTION_HANDLER); - return answer == null ? null : BucketInfo.fromPb(answer); + return answer == null ? null : Bucket.fromPb(this, answer); } catch (RetryHelperException e) { throw StorageException.translateAndThrow(e); } } @Override - public BlobInfo get(String bucket, String blob, BlobGetOption... options) { + public Blob get(String bucket, String blob, BlobGetOption... options) { return get(BlobId.of(bucket, blob), options); } @Override - public BlobInfo get(BlobId blob, BlobGetOption... options) { + public Blob get(BlobId blob, BlobGetOption... options) { final StorageObject storedObject = blob.toPb(); final Map optionsMap = optionMap(blob, options); try { @@ -178,18 +185,18 @@ public StorageObject call() { return storageRpc.get(storedObject, optionsMap); } }, options().retryParams(), EXCEPTION_HANDLER); - return storageObject == null ? null : BlobInfo.fromPb(storageObject); + return storageObject == null ? null : Blob.fromPb(this, storageObject); } catch (RetryHelperException e) { throw StorageException.translateAndThrow(e); } } @Override - public BlobInfo get(BlobId blob) { + public Blob get(BlobId blob) { return get(blob, new BlobGetOption[0]); } - private static class BucketPageFetcher implements NextPageFetcher { + private static class BucketPageFetcher implements NextPageFetcher { private static final long serialVersionUID = 5850406828803613729L; private final Map requestOptions; @@ -204,12 +211,12 @@ private static class BucketPageFetcher implements NextPageFetcher { } @Override - public Page nextPage() { + public Page nextPage() { return listBuckets(serviceOptions, requestOptions); } } - private static class BlobPageFetcher implements NextPageFetcher { + private static class BlobPageFetcher implements NextPageFetcher { private static final long serialVersionUID = 81807334445874098L; private final Map requestOptions; @@ -225,22 +232,22 @@ private static class BlobPageFetcher implements NextPageFetcher { } @Override - public Page nextPage() { + public Page nextPage() { return listBlobs(bucket, serviceOptions, requestOptions); } } @Override - public Page list(BucketListOption... options) { + public Page list(BucketListOption... options) { return listBuckets(options(), optionMap(options)); } @Override - public Page list(final String bucket, BlobListOption... options) { + public Page list(final String bucket, BlobListOption... options) { return listBlobs(bucket, options(), optionMap(options)); } - private static Page listBuckets(final StorageOptions serviceOptions, + private static Page listBuckets(final StorageOptions serviceOptions, final Map optionsMap) { try { Tuple> result = runWithRetries( @@ -251,22 +258,23 @@ public Tuple> cal } }, serviceOptions.retryParams(), EXCEPTION_HANDLER); String cursor = result.x(); - Iterable buckets = - result.y() == null ? ImmutableList.of() : Iterables.transform(result.y(), - new Function() { + Iterable buckets = + result.y() == null ? ImmutableList.of() : Iterables.transform(result.y(), + new Function() { @Override - public BucketInfo apply(com.google.api.services.storage.model.Bucket bucketPb) { - return BucketInfo.fromPb(bucketPb); + public Bucket apply(com.google.api.services.storage.model.Bucket bucketPb) { + return Bucket.fromPb(serviceOptions.service(), bucketPb); } }); - return new PageImpl<>(new BucketPageFetcher(serviceOptions, cursor, optionsMap), cursor, + return new PageImpl<>( + new BucketPageFetcher(serviceOptions, cursor, optionsMap), cursor, buckets); } catch (RetryHelperException e) { throw StorageException.translateAndThrow(e); } } - private static Page listBlobs(final String bucket, + private static Page listBlobs(final String bucket, final StorageOptions serviceOptions, final Map optionsMap) { try { Tuple> result = runWithRetries( @@ -277,15 +285,17 @@ public Tuple> call() { } }, serviceOptions.retryParams(), EXCEPTION_HANDLER); String cursor = result.x(); - Iterable blobs = - result.y() == null ? ImmutableList.of() : Iterables.transform(result.y(), - new Function() { + Iterable blobs = + result.y() == null + ? ImmutableList.of() + : Iterables.transform(result.y(), new Function() { @Override - public BlobInfo apply(StorageObject storageObject) { - return BlobInfo.fromPb(storageObject); + public Blob apply(StorageObject storageObject) { + return Blob.fromPb(serviceOptions.service(), storageObject); } }); - return new PageImpl<>(new BlobPageFetcher(bucket, serviceOptions, cursor, optionsMap), + return new PageImpl<>( + new BlobPageFetcher(bucket, serviceOptions, cursor, optionsMap), cursor, blobs); } catch (RetryHelperException e) { @@ -294,11 +304,11 @@ public BlobInfo apply(StorageObject storageObject) { } @Override - public BucketInfo update(BucketInfo bucketInfo, BucketTargetOption... options) { + public Bucket update(BucketInfo bucketInfo, BucketTargetOption... options) { final com.google.api.services.storage.model.Bucket bucketPb = bucketInfo.toPb(); final Map optionsMap = optionMap(bucketInfo, options); try { - return BucketInfo.fromPb(runWithRetries( + return Bucket.fromPb(this, runWithRetries( new Callable() { @Override public com.google.api.services.storage.model.Bucket call() { @@ -311,11 +321,11 @@ public com.google.api.services.storage.model.Bucket call() { } @Override - public BlobInfo update(BlobInfo blobInfo, BlobTargetOption... options) { + public Blob update(BlobInfo blobInfo, BlobTargetOption... options) { final StorageObject storageObject = blobInfo.toPb(); final Map optionsMap = optionMap(blobInfo, options); try { - return BlobInfo.fromPb(runWithRetries(new Callable() { + return Blob.fromPb(this, runWithRetries(new Callable() { @Override public StorageObject call() { return storageRpc.patch(storageObject, optionsMap); @@ -327,7 +337,7 @@ public StorageObject call() { } @Override - public BlobInfo update(BlobInfo blobInfo) { + public Blob update(BlobInfo blobInfo) { return update(blobInfo, new BlobTargetOption[0]); } @@ -374,7 +384,7 @@ public boolean delete(BlobId blob) { } @Override - public BlobInfo compose(final ComposeRequest composeRequest) { + public Blob compose(final ComposeRequest composeRequest) { final List sources = Lists.newArrayListWithCapacity(composeRequest.sourceBlobs().size()); for (ComposeRequest.SourceBlob sourceBlob : composeRequest.sourceBlobs()) { @@ -386,7 +396,7 @@ public BlobInfo compose(final ComposeRequest composeRequest) { final Map targetOptions = optionMap(composeRequest.target().generation(), composeRequest.target().metageneration(), composeRequest.targetOptions()); try { - return BlobInfo.fromPb(runWithRetries(new Callable() { + return Blob.fromPb(this, runWithRetries(new Callable() { @Override public StorageObject call() { return storageRpc.compose(sources, target, targetOptions); @@ -468,18 +478,19 @@ public BatchResponse submit(BatchRequest batchRequest) { } StorageRpc.BatchResponse response = storageRpc.batch(new StorageRpc.BatchRequest(toDelete, toUpdate, toGet)); - List> deletes = transformBatchResult( - toDelete, response.deletes, Functions.identity()); - List> updates = transformBatchResult( - toUpdate, response.updates, BlobInfo.FROM_PB_FUNCTION); - List> gets = transformBatchResult( - toGet, response.gets, BlobInfo.FROM_PB_FUNCTION); + List> deletes = + transformBatchResult(toDelete, response.deletes, DELETE_FUNCTION); + List> updates = + transformBatchResult(toUpdate, response.updates, Blob.BLOB_FROM_PB_FUNCTION); + List> gets = + transformBatchResult(toGet, response.gets, Blob.BLOB_FROM_PB_FUNCTION); return new BatchResponse(deletes, updates, gets); } private List> transformBatchResult( Iterable>> request, - Map> results, Function transform) { + Map> results, + Function, O> transform) { List> response = Lists.newArrayListWithCapacity(results.size()); for (Tuple tuple : request) { Tuple result = results.get(tuple.x()); @@ -489,7 +500,8 @@ private List> transformBatch response.add(new BatchResponse.Result(exception)); } else { response.add(object != null - ? BatchResponse.Result.of(transform.apply(object)) : BatchResponse.Result.empty()); + ? BatchResponse.Result.of(transform.apply(Tuple.of((Storage) this, object))) + : BatchResponse.Result.empty()); } } return response; @@ -587,7 +599,7 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio } @Override - public List get(BlobId... blobIds) { + public List get(BlobId... blobIds) { BatchRequest.Builder requestBuilder = BatchRequest.builder(); for (BlobId blob : blobIds) { requestBuilder.get(blob); @@ -597,7 +609,7 @@ public List get(BlobId... blobIds) { } @Override - public List update(BlobInfo... blobInfos) { + public List update(BlobInfo... blobInfos) { BatchRequest.Builder requestBuilder = BatchRequest.builder(); for (BlobInfo blobInfo : blobInfos) { requestBuilder.update(blobInfo); diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/package-info.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/package-info.java index fda14ea2e808..4609c7034693 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/package-info.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/package-info.java @@ -21,7 +21,7 @@ *
 {@code
  * Storage storage = StorageOptions.defaultInstance().service();
  * BlobId blobId = BlobId.of("bucket", "blob_name");
- * Blob blob = Blob.get(storage, blobId);
+ * Blob blob = storage.get(blobId);
  * if (blob == null) {
  *   BlobInfo blobInfo = BlobInfo.builder(blobId).contentType("text/plain").build();
  *   storage.create(blobInfo, "Hello, Cloud Storage!".getBytes(UTF_8));
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BatchResponseTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BatchResponseTest.java
index 5985329e0183..eb45b8b51271 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BatchResponseTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/BatchResponseTest.java
@@ -22,22 +22,35 @@
 import com.google.common.collect.ImmutableList;
 import com.google.gcloud.storage.BatchResponse.Result;
 
+import org.easymock.EasyMock;
+import org.junit.Before;
 import org.junit.Test;
 
 import java.util.List;
 
 public class BatchResponseTest {
 
-  private static final BlobInfo BLOB_INFO_1 = BlobInfo.builder("b", "o1").build();
-  private static final BlobInfo BLOB_INFO_2 = BlobInfo.builder("b", "o2").build();
-  private static final BlobInfo BLOB_INFO_3 = BlobInfo.builder("b", "o3").build();
+  private Storage mockStorage;
+  private Blob blob1;
+  private Blob blob2;
+  private Blob blob3;
+
+  @Before
+  public void setUp() {
+    mockStorage = EasyMock.createMock(Storage.class);
+    EasyMock.expect(mockStorage.options()).andReturn(null).times(3);
+    EasyMock.replay(mockStorage);
+    blob1 = new Blob(mockStorage, new BlobInfo.BuilderImpl(BlobInfo.builder("b", "o1").build()));
+    blob2 = new Blob(mockStorage, new BlobInfo.BuilderImpl(BlobInfo.builder("b", "o2").build()));
+    blob3 = new Blob(mockStorage, new BlobInfo.BuilderImpl(BlobInfo.builder("b", "o3").build()));
+  }
 
   @Test
   public void testBatchResponse() {
     List> deletes = ImmutableList.of(Result.of(true), Result.of(false));
-    List> updates =
-        ImmutableList.of(Result.of(BLOB_INFO_1), Result.of(BLOB_INFO_2));
-    List> gets = ImmutableList.of(Result.of(BLOB_INFO_2), Result.of(BLOB_INFO_3));
+    List> updates =
+        ImmutableList.of(Result.of(blob1), Result.of(blob2));
+    List> gets = ImmutableList.of(Result.of(blob2), Result.of(blob3));
     BatchResponse response = new BatchResponse(deletes, updates, gets);
     assertEquals(deletes, response.deletes());
     assertEquals(updates, response.updates());
@@ -47,14 +60,13 @@ public void testBatchResponse() {
   @Test
   public void testEquals() {
     List> deletes = ImmutableList.of(Result.of(true), Result.of(false));
-    List> updates =
-        ImmutableList.of(Result.of(BLOB_INFO_1), Result.of(BLOB_INFO_2));
-    List> gets = ImmutableList.of(Result.of(BLOB_INFO_2), Result.of(BLOB_INFO_3));
+    List> updates =
+        ImmutableList.of(Result.of(blob1), Result.of(blob2));
+    List> gets = ImmutableList.of(Result.of(blob2), Result.of(blob3));
     List> otherDeletes = ImmutableList.of(Result.of(false), Result.of(true));
-    List> otherUpdates =
-        ImmutableList.of(Result.of(BLOB_INFO_2), Result.of(BLOB_INFO_3));
-    List> otherGets =
-        ImmutableList.of(Result.of(BLOB_INFO_1), Result.of(BLOB_INFO_2));
+    List> otherUpdates = ImmutableList.of(Result.of(blob2), Result.of(blob3));
+    List> otherGets =
+        ImmutableList.of(Result.of(blob1), Result.of(blob2));
     BatchResponse response = new BatchResponse(deletes, updates, gets);
     BatchResponse responseEquals = new BatchResponse(deletes, updates, gets);
     BatchResponse responseNotEquals1 = new BatchResponse(otherDeletes, updates, gets);
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 586e7fd0fd39..c7508593f8c9 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
@@ -16,22 +16,28 @@
 
 package com.google.gcloud.storage;
 
+import static com.google.gcloud.storage.Acl.Project.ProjectRole.VIEWERS;
+import static com.google.gcloud.storage.Acl.Role.READER;
+import static com.google.gcloud.storage.Acl.Role.WRITER;
 import static org.easymock.EasyMock.capture;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.createStrictMock;
+import static org.easymock.EasyMock.eq;
 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.assertNotNull;
 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.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.gcloud.ReadChannel;
+import com.google.gcloud.storage.Acl.Project;
+import com.google.gcloud.storage.Acl.User;
 import com.google.gcloud.storage.Storage.CopyRequest;
 
 import org.easymock.Capture;
@@ -40,25 +46,65 @@
 import org.junit.Test;
 
 import java.net.URL;
-import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 public class BlobTest {
 
+  private static final List ACL = ImmutableList.of(
+      Acl.of(User.ofAllAuthenticatedUsers(), READER), Acl.of(new Project(VIEWERS, "p1"), WRITER));
+  private static final Integer COMPONENT_COUNT = 2;
+  private static final String CONTENT_TYPE = "text/html";
+  private static final String CACHE_CONTROL = "cache";
+  private static final String CONTENT_DISPOSITION = "content-disposition";
+  private static final String CONTENT_ENCODING = "UTF-8";
+  private static final String CONTENT_LANGUAGE = "En";
+  private static final String CRC32 = "0xFF00";
+  private static final Long DELETE_TIME = System.currentTimeMillis();
+  private static final String ETAG = "0xFF00";
+  private static final Long GENERATION = 1L;
+  private static final String ID = "B/N:1";
+  private static final String MD5 = "0xFF00";
+  private static final String MEDIA_LINK = "http://media/b/n";
+  private static final Map METADATA = ImmutableMap.of("n1", "v1", "n2", "v2");
+  private static final Long META_GENERATION = 10L;
+  private static final User OWNER = new User("user@gmail.com");
+  private static final String SELF_LINK = "http://storage/b/n";
+  private static final Long SIZE = 1024L;
+  private static final Long UPDATE_TIME = DELETE_TIME - 1L;
+  private static final BlobInfo FULL_BLOB_INFO = BlobInfo.builder("b", "n", GENERATION)
+      .acl(ACL)
+      .componentCount(COMPONENT_COUNT)
+      .contentType(CONTENT_TYPE)
+      .cacheControl(CACHE_CONTROL)
+      .contentDisposition(CONTENT_DISPOSITION)
+      .contentEncoding(CONTENT_ENCODING)
+      .contentLanguage(CONTENT_LANGUAGE)
+      .crc32c(CRC32)
+      .deleteTime(DELETE_TIME)
+      .etag(ETAG)
+      .id(ID)
+      .md5(MD5)
+      .mediaLink(MEDIA_LINK)
+      .metadata(METADATA)
+      .metageneration(META_GENERATION)
+      .owner(OWNER)
+      .selfLink(SELF_LINK)
+      .size(SIZE)
+      .updateTime(UPDATE_TIME)
+      .build();
   private static final BlobInfo BLOB_INFO = BlobInfo.builder("b", "n").metageneration(42L).build();
-  private static final BlobId[] BLOB_ID_ARRAY = {BlobId.of("b1", "n1"),
-      BlobId.of("b2", "n2"), BlobId.of("b3", "n3")};
-  private static final BlobInfo[] BLOB_INFO_ARRAY = {BlobInfo.builder("b1", "n1").build(),
-      BlobInfo.builder("b2", "n2").build(), BlobInfo.builder("b3", "n3").build()};
 
   private Storage storage;
   private Blob blob;
+  private Blob expectedBlob;
+  private Storage serviceMockReturnsOptions = createMock(Storage.class);
+  private StorageOptions mockOptions = createMock(StorageOptions.class);
 
   @Before
-  public void setUp() throws Exception {
+  public void setUp() {
     storage = createStrictMock(Storage.class);
-    blob = new Blob(storage, BLOB_INFO);
   }
 
   @After
@@ -66,91 +112,122 @@ public void tearDown() throws Exception {
     verify(storage);
   }
 
-  @Test
-  public void testInfo() throws Exception {
-    assertEquals(BLOB_INFO, blob.info());
-    replay(storage);
+  private void initializeExpectedBlob(int optionsCalls) {
+    expect(serviceMockReturnsOptions.options()).andReturn(mockOptions).times(optionsCalls);
+    replay(serviceMockReturnsOptions);
+    expectedBlob = new Blob(serviceMockReturnsOptions, new BlobInfo.BuilderImpl(BLOB_INFO));
+  }
+
+  private void initializeBlob() {
+    blob = new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO));
   }
 
   @Test
   public void testExists_True() throws Exception {
+    initializeExpectedBlob(1);
     Storage.BlobGetOption[] expectedOptions = {Storage.BlobGetOption.fields()};
-    expect(storage.get(BLOB_INFO.blobId(), expectedOptions)).andReturn(BLOB_INFO);
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.get(expectedBlob.blobId(), expectedOptions)).andReturn(expectedBlob);
     replay(storage);
+    initializeBlob();
     assertTrue(blob.exists());
   }
 
   @Test
   public void testExists_False() throws Exception {
     Storage.BlobGetOption[] expectedOptions = {Storage.BlobGetOption.fields()};
+    expect(storage.options()).andReturn(null);
     expect(storage.get(BLOB_INFO.blobId(), expectedOptions)).andReturn(null);
     replay(storage);
+    initializeBlob();
     assertFalse(blob.exists());
   }
 
   @Test
   public void testContent() throws Exception {
+    initializeExpectedBlob(2);
     byte[] content = {1, 2};
+    expect(storage.options()).andReturn(mockOptions);
     expect(storage.readAllBytes(BLOB_INFO.blobId())).andReturn(content);
     replay(storage);
+    initializeBlob();
     assertArrayEquals(content, blob.content());
   }
 
   @Test
   public void testReload() throws Exception {
-    BlobInfo updatedInfo = BLOB_INFO.toBuilder().cacheControl("c").build();
-    expect(storage.get(BLOB_INFO.blobId(), new Storage.BlobGetOption[0])).andReturn(updatedInfo);
+    initializeExpectedBlob(2);
+    Blob expectedReloadedBlob = expectedBlob.toBuilder().cacheControl("c").build();
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.get(BLOB_INFO.blobId(), new Storage.BlobGetOption[0]))
+        .andReturn(expectedReloadedBlob);
     replay(storage);
+    initializeBlob();
     Blob updatedBlob = blob.reload();
-    assertSame(storage, updatedBlob.storage());
-    assertEquals(updatedInfo, updatedBlob.info());
+    assertEquals(expectedReloadedBlob, updatedBlob);
   }
 
   @Test
   public void testReloadNull() throws Exception {
+    initializeExpectedBlob(1);
+    expect(storage.options()).andReturn(mockOptions);
     expect(storage.get(BLOB_INFO.blobId(), new Storage.BlobGetOption[0])).andReturn(null);
     replay(storage);
-    assertNull(blob.reload());
+    initializeBlob();
+    Blob reloadedBlob = blob.reload();
+    assertNull(reloadedBlob);
   }
 
   @Test
   public void testReloadWithOptions() throws Exception {
-    BlobInfo updatedInfo = BLOB_INFO.toBuilder().cacheControl("c").build();
+    initializeExpectedBlob(2);
+    Blob expectedReloadedBlob = expectedBlob.toBuilder().cacheControl("c").build();
     Storage.BlobGetOption[] options = {Storage.BlobGetOption.metagenerationMatch(42L)};
-    expect(storage.get(BLOB_INFO.blobId(), options)).andReturn(updatedInfo);
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.get(BLOB_INFO.blobId(), options)).andReturn(expectedReloadedBlob);
     replay(storage);
+    initializeBlob();
     Blob updatedBlob = blob.reload(Blob.BlobSourceOption.metagenerationMatch());
-    assertSame(storage, updatedBlob.storage());
-    assertEquals(updatedInfo, updatedBlob.info());
+    assertEquals(expectedReloadedBlob, updatedBlob);
   }
 
   @Test
   public void testUpdate() throws Exception {
-    BlobInfo updatedInfo = BLOB_INFO.toBuilder().cacheControl("c").build();
-    expect(storage.update(updatedInfo, new Storage.BlobTargetOption[0])).andReturn(updatedInfo);
+    initializeExpectedBlob(2);
+    Blob expectedUpdatedBlob = expectedBlob.toBuilder().cacheControl("c").build();
+    expect(storage.options()).andReturn(mockOptions).times(2);
+    expect(storage.update(eq(expectedUpdatedBlob), new Storage.BlobTargetOption[0]))
+        .andReturn(expectedUpdatedBlob);
     replay(storage);
-    Blob updatedBlob = blob.update(updatedInfo);
-    assertSame(storage, blob.storage());
-    assertEquals(updatedInfo, updatedBlob.info());
+    initializeBlob();
+    Blob updatedBlob = new Blob(storage, new BlobInfo.BuilderImpl(expectedUpdatedBlob));
+    Blob actualUpdatedBlob = updatedBlob.update();
+    assertEquals(expectedUpdatedBlob, actualUpdatedBlob);
   }
 
   @Test
   public void testDelete() throws Exception {
+    initializeExpectedBlob(2);
+    expect(storage.options()).andReturn(mockOptions);
     expect(storage.delete(BLOB_INFO.blobId(), new Storage.BlobSourceOption[0])).andReturn(true);
     replay(storage);
+    initializeBlob();
     assertTrue(blob.delete());
   }
 
   @Test
   public void testCopyToBucket() throws Exception {
+    initializeExpectedBlob(2);
     BlobInfo target = BlobInfo.builder(BlobId.of("bt", "n")).build();
     CopyWriter copyWriter = createMock(CopyWriter.class);
     Capture capturedCopyRequest = Capture.newInstance();
+    expect(storage.options()).andReturn(mockOptions);
     expect(storage.copy(capture(capturedCopyRequest))).andReturn(copyWriter);
     replay(storage);
+    initializeBlob();
     CopyWriter returnedCopyWriter = blob.copyTo("bt");
     assertEquals(copyWriter, returnedCopyWriter);
-    assertEquals(capturedCopyRequest.getValue().source(), blob.id());
+    assertEquals(capturedCopyRequest.getValue().source(), blob.blobId());
     assertEquals(capturedCopyRequest.getValue().target(), target);
     assertTrue(capturedCopyRequest.getValue().sourceOptions().isEmpty());
     assertTrue(capturedCopyRequest.getValue().targetOptions().isEmpty());
@@ -158,14 +235,17 @@ public void testCopyToBucket() throws Exception {
 
   @Test
   public void testCopyTo() throws Exception {
+    initializeExpectedBlob(2);
     BlobInfo target = BlobInfo.builder(BlobId.of("bt", "nt")).build();
     CopyWriter copyWriter = createMock(CopyWriter.class);
     Capture capturedCopyRequest = Capture.newInstance();
+    expect(storage.options()).andReturn(mockOptions);
     expect(storage.copy(capture(capturedCopyRequest))).andReturn(copyWriter);
     replay(storage);
+    initializeBlob();
     CopyWriter returnedCopyWriter = blob.copyTo("bt", "nt");
     assertEquals(copyWriter, returnedCopyWriter);
-    assertEquals(capturedCopyRequest.getValue().source(), blob.id());
+    assertEquals(capturedCopyRequest.getValue().source(), blob.blobId());
     assertEquals(capturedCopyRequest.getValue().target(), target);
     assertTrue(capturedCopyRequest.getValue().sourceOptions().isEmpty());
     assertTrue(capturedCopyRequest.getValue().targetOptions().isEmpty());
@@ -173,15 +253,18 @@ public void testCopyTo() throws Exception {
 
   @Test
   public void testCopyToBlobId() throws Exception {
+    initializeExpectedBlob(2);
     BlobId targetId = BlobId.of("bt", "nt");
     CopyWriter copyWriter = createMock(CopyWriter.class);
     BlobInfo target = BlobInfo.builder(targetId).build();
     Capture capturedCopyRequest = Capture.newInstance();
+    expect(storage.options()).andReturn(mockOptions);
     expect(storage.copy(capture(capturedCopyRequest))).andReturn(copyWriter);
     replay(storage);
+    initializeBlob();
     CopyWriter returnedCopyWriter = blob.copyTo(targetId);
     assertEquals(copyWriter, returnedCopyWriter);
-    assertEquals(capturedCopyRequest.getValue().source(), blob.id());
+    assertEquals(capturedCopyRequest.getValue().source(), blob.blobId());
     assertEquals(capturedCopyRequest.getValue().target(), target);
     assertTrue(capturedCopyRequest.getValue().sourceOptions().isEmpty());
     assertTrue(capturedCopyRequest.getValue().targetOptions().isEmpty());
@@ -189,174 +272,93 @@ public void testCopyToBlobId() throws Exception {
 
   @Test
   public void testReader() throws Exception {
+    initializeExpectedBlob(2);
     ReadChannel channel = createMock(ReadChannel.class);
+    expect(storage.options()).andReturn(mockOptions);
     expect(storage.reader(BLOB_INFO.blobId())).andReturn(channel);
     replay(storage);
+    initializeBlob();
     assertSame(channel, blob.reader());
   }
 
   @Test
   public void testWriter() throws Exception {
+    initializeExpectedBlob(2);
     BlobWriteChannel channel = createMock(BlobWriteChannel.class);
-    expect(storage.writer(BLOB_INFO)).andReturn(channel);
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.writer(eq(expectedBlob))).andReturn(channel);
     replay(storage);
+    initializeBlob();
     assertSame(channel, blob.writer());
   }
 
   @Test
   public void testSignUrl() throws Exception {
+    initializeExpectedBlob(2);
     URL url = new URL("http://localhost:123/bla");
-    expect(storage.signUrl(BLOB_INFO, 100, TimeUnit.SECONDS)).andReturn(url);
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.signUrl(expectedBlob, 100, TimeUnit.SECONDS)).andReturn(url);
     replay(storage);
+    initializeBlob();
     assertEquals(url, blob.signUrl(100, TimeUnit.SECONDS));
   }
 
   @Test
-  public void testGetSome() throws Exception {
-    List blobInfoList = Arrays.asList(BLOB_INFO_ARRAY);
-    expect(storage.get(BLOB_ID_ARRAY)).andReturn(blobInfoList);
-    replay(storage);
-    List result = Blob.get(storage, BLOB_ID_ARRAY[0], BLOB_ID_ARRAY[1], BLOB_ID_ARRAY[2]);
-    assertEquals(blobInfoList.size(), result.size());
-    for (int i = 0; i < blobInfoList.size(); i++) {
-      assertEquals(blobInfoList.get(i), result.get(i).info());
-    }
-  }
-
-  @Test
-  public void testGetSomeList() throws Exception {
-    List blobInfoList = Arrays.asList(BLOB_INFO_ARRAY);
-    expect(storage.get(BLOB_ID_ARRAY)).andReturn(blobInfoList);
-    replay(storage);
-    List result = Blob.get(storage, Arrays.asList(BLOB_ID_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_ID_ARRAY)).andReturn(blobInfoList);
-    replay(storage);
-    List result = Blob.get(storage, BLOB_ID_ARRAY[0], BLOB_ID_ARRAY[1], BLOB_ID_ARRAY[2]);
-    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_ID_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 deleteResult = Arrays.asList(true, true, true);
-    expect(storage.delete(BLOB_ID_ARRAY)).andReturn(deleteResult);
-    replay(storage);
-    List result = Blob.delete(storage, BLOB_ID_ARRAY);
-    assertEquals(deleteResult.size(), result.size());
-    for (int i = 0; i < deleteResult.size(); i++) {
-      assertEquals(deleteResult.get(i), result.get(i));
-    }
-  }
-
-  @Test
-  public void testGetFromString() throws Exception {
-    expect(storage.get(BLOB_INFO.blobId(), new Storage.BlobGetOption[0])).andReturn(BLOB_INFO);
-    replay(storage);
-    Blob loadedBlob = Blob.get(storage, BLOB_INFO.bucket(), BLOB_INFO.name());
-    assertEquals(BLOB_INFO, loadedBlob.info());
-  }
-
-  @Test
-  public void testGetFromId() throws Exception {
-    expect(storage.get(BLOB_INFO.blobId(), new Storage.BlobGetOption[0])).andReturn(BLOB_INFO);
-    replay(storage);
-    Blob loadedBlob = Blob.get(storage, BLOB_INFO.blobId());
-    assertNotNull(loadedBlob);
-    assertEquals(BLOB_INFO, loadedBlob.info());
-  }
-
-  @Test
-  public void testGetFromStringNull() throws Exception {
-    expect(storage.get(BLOB_INFO.blobId(), new Storage.BlobGetOption[0])).andReturn(null);
-    replay(storage);
-    assertNull(Blob.get(storage, BLOB_INFO.bucket(), BLOB_INFO.name()));
-  }
-
-  @Test
-  public void testGetFromIdNull() throws Exception {
-    expect(storage.get(BLOB_INFO.blobId(), new Storage.BlobGetOption[0])).andReturn(null);
-    replay(storage);
-    assertNull(Blob.get(storage, BLOB_INFO.blobId()));
-  }
-
-  @Test
-  public void testGetFromStringWithOptions() throws Exception {
-    expect(storage.get(BLOB_INFO.blobId(), Storage.BlobGetOption.generationMatch(42L)))
-        .andReturn(BLOB_INFO);
+  public void testToBuilder() {
+    expect(storage.options()).andReturn(mockOptions).times(4);
     replay(storage);
-    Blob loadedBlob = Blob.get(storage, BLOB_INFO.bucket(), BLOB_INFO.name(),
-        Storage.BlobGetOption.generationMatch(42L));
-    assertEquals(BLOB_INFO, loadedBlob.info());
+    Blob fullBlob = new Blob(storage, new BlobInfo.BuilderImpl(FULL_BLOB_INFO));
+    assertEquals(fullBlob, fullBlob.toBuilder().build());
+    Blob simpleBlob = new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO));
+    assertEquals(simpleBlob, simpleBlob.toBuilder().build());
   }
 
   @Test
-  public void testGetFromIdWithOptions() throws Exception {
-    expect(storage.get(BLOB_INFO.blobId(), Storage.BlobGetOption.generationMatch(42L)))
-        .andReturn(BLOB_INFO);
+  public void testBuilder() {
+    initializeExpectedBlob(4);
+    expect(storage.options()).andReturn(mockOptions).times(2);
     replay(storage);
-    Blob loadedBlob =
-        Blob.get(storage, BLOB_INFO.blobId(), Storage.BlobGetOption.generationMatch(42L));
-    assertNotNull(loadedBlob);
-    assertEquals(BLOB_INFO, loadedBlob.info());
+    Blob.Builder builder = new Blob.Builder(new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO)));
+    Blob blob = builder.acl(ACL)
+        .componentCount(COMPONENT_COUNT)
+        .contentType(CONTENT_TYPE)
+        .cacheControl(CACHE_CONTROL)
+        .contentDisposition(CONTENT_DISPOSITION)
+        .contentEncoding(CONTENT_ENCODING)
+        .contentLanguage(CONTENT_LANGUAGE)
+        .crc32c(CRC32)
+        .deleteTime(DELETE_TIME)
+        .etag(ETAG)
+        .id(ID)
+        .md5(MD5)
+        .mediaLink(MEDIA_LINK)
+        .metadata(METADATA)
+        .metageneration(META_GENERATION)
+        .owner(OWNER)
+        .selfLink(SELF_LINK)
+        .size(SIZE)
+        .updateTime(UPDATE_TIME)
+        .build();
+    assertEquals("b", blob.bucket());
+    assertEquals("n", blob.name());
+    assertEquals(ACL, blob.acl());
+    assertEquals(COMPONENT_COUNT, blob.componentCount());
+    assertEquals(CONTENT_TYPE, blob.contentType());
+    assertEquals(CACHE_CONTROL, blob.cacheControl());
+    assertEquals(CONTENT_DISPOSITION, blob.contentDisposition());
+    assertEquals(CONTENT_ENCODING, blob.contentEncoding());
+    assertEquals(CONTENT_LANGUAGE, blob.contentLanguage());
+    assertEquals(CRC32, blob.crc32c());
+    assertEquals(DELETE_TIME, blob.deleteTime());
+    assertEquals(ETAG, blob.etag());
+    assertEquals(ID, blob.id());
+    assertEquals(MD5, blob.md5());
+    assertEquals(MEDIA_LINK, blob.mediaLink());
+    assertEquals(METADATA, blob.metadata());
+    assertEquals(META_GENERATION, blob.metageneration());
+    assertEquals(OWNER, blob.owner());
+    assertEquals(SELF_LINK, blob.selfLink());
+    assertEquals(SIZE, blob.size());
+    assertEquals(UPDATE_TIME, blob.updateTime());
   }
 }
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 4e253033c6f2..aac8a79d3a47 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
@@ -16,22 +16,28 @@
 
 package com.google.gcloud.storage;
 
+import static com.google.gcloud.storage.Acl.Project.ProjectRole.VIEWERS;
+import static com.google.gcloud.storage.Acl.Role.READER;
+import static com.google.gcloud.storage.Acl.Role.WRITER;
 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.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 import com.google.common.collect.ImmutableList;
 import com.google.gcloud.Page;
 import com.google.gcloud.PageImpl;
+import com.google.gcloud.storage.Acl.Project;
+import com.google.gcloud.storage.Acl.User;
 import com.google.gcloud.storage.BatchResponse.Result;
+import com.google.gcloud.storage.BucketInfo.AgeDeleteRule;
+import com.google.gcloud.storage.BucketInfo.DeleteRule;
 
 import org.easymock.Capture;
 import org.junit.After;
@@ -48,20 +54,54 @@
 
 public class BucketTest {
 
+  private static final List ACL = ImmutableList.of(
+      Acl.of(User.ofAllAuthenticatedUsers(), READER), Acl.of(new Project(VIEWERS, "p1"), WRITER));
+  private static final String ETAG = "0xFF00";
+  private static final String ID = "B/N:1";
+  private static final Long META_GENERATION = 10L;
+  private static final User OWNER = new User("user@gmail.com");
+  private static final String SELF_LINK = "http://storage/b/n";
+  private static final Long CREATE_TIME = System.currentTimeMillis();
+  private static final List CORS = Collections.singletonList(Cors.builder().build());
+  private static final List DEFAULT_ACL =
+      Collections.singletonList(Acl.of(User.ofAllAuthenticatedUsers(), WRITER));
+  private static final List DELETE_RULES =
+      Collections.singletonList(new AgeDeleteRule(5));
+  private static final String INDEX_PAGE = "index.html";
+  private static final String NOT_FOUND_PAGE = "error.html";
+  private static final String LOCATION = "ASIA";
+  private static final String STORAGE_CLASS = "STANDARD";
+  private static final Boolean VERSIONING_ENABLED = true;
+  private static final BucketInfo FULL_BUCKET_INFO = BucketInfo.builder("b")
+      .acl(ACL)
+      .etag(ETAG)
+      .id(ID)
+      .metageneration(META_GENERATION)
+      .owner(OWNER)
+      .selfLink(SELF_LINK)
+      .cors(CORS)
+      .createTime(CREATE_TIME)
+      .defaultAcl(DEFAULT_ACL)
+      .deleteRules(DELETE_RULES)
+      .indexPage(INDEX_PAGE)
+      .notFoundPage(NOT_FOUND_PAGE)
+      .location(LOCATION)
+      .storageClass(STORAGE_CLASS)
+      .versioningEnabled(VERSIONING_ENABLED)
+      .build();
   private static final BucketInfo BUCKET_INFO = BucketInfo.builder("b").metageneration(42L).build();
-  private static final Iterable BLOB_INFO_RESULTS = ImmutableList.of(
-      BlobInfo.builder("b", "n1").build(),
-      BlobInfo.builder("b", "n2").build(),
-      BlobInfo.builder("b", "n3").build());
   private static final String CONTENT_TYPE = "text/plain";
 
   private Storage storage;
+  private Storage serviceMockReturnsOptions = createMock(Storage.class);
+  private StorageOptions mockOptions = createMock(StorageOptions.class);
   private Bucket bucket;
+  private Bucket expectedBucket;
+  private Iterable blobResults;
 
   @Before
-  public void setUp() throws Exception {
+  public void setUp() {
     storage = createStrictMock(Storage.class);
-    bucket = new Bucket(storage, BUCKET_INFO);
   }
 
   @After
@@ -69,124 +109,165 @@ public void tearDown() throws Exception {
     verify(storage);
   }
 
-  @Test
-  public void testInfo() throws Exception {
-    assertEquals(BUCKET_INFO, bucket.info());
-    replay(storage);
+  private void initializeExpectedBucket(int optionsCalls) {
+    expect(serviceMockReturnsOptions.options()).andReturn(mockOptions).times(optionsCalls);
+    replay(serviceMockReturnsOptions);
+    expectedBucket = new Bucket(serviceMockReturnsOptions, new BucketInfo.BuilderImpl(BUCKET_INFO));
+    blobResults = ImmutableList.of(
+        new Blob(serviceMockReturnsOptions,
+            new BlobInfo.BuilderImpl(BlobInfo.builder("b", "n1").build())),
+        new Blob(serviceMockReturnsOptions,
+            new BlobInfo.BuilderImpl(BlobInfo.builder("b", "n2").build())),
+        new Blob(serviceMockReturnsOptions,
+            new BlobInfo.BuilderImpl(BlobInfo.builder("b", "n3").build())));
+  }
+
+  private void initializeBucket() {
+    bucket = new Bucket(storage, new BucketInfo.BuilderImpl(BUCKET_INFO));
   }
 
   @Test
   public void testExists_True() throws Exception {
+    initializeExpectedBucket(4);
     Storage.BucketGetOption[] expectedOptions = {Storage.BucketGetOption.fields()};
-    expect(storage.get(BUCKET_INFO.name(), expectedOptions)).andReturn(BUCKET_INFO);
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.get(BUCKET_INFO.name(), expectedOptions)).andReturn(expectedBucket);
     replay(storage);
+    initializeBucket();
     assertTrue(bucket.exists());
   }
 
   @Test
   public void testExists_False() throws Exception {
+    initializeExpectedBucket(4);
     Storage.BucketGetOption[] expectedOptions = {Storage.BucketGetOption.fields()};
+    expect(storage.options()).andReturn(mockOptions);
     expect(storage.get(BUCKET_INFO.name(), expectedOptions)).andReturn(null);
     replay(storage);
+    initializeBucket();
     assertFalse(bucket.exists());
   }
 
   @Test
   public void testReload() throws Exception {
+    initializeExpectedBucket(5);
     BucketInfo updatedInfo = BUCKET_INFO.toBuilder().notFoundPage("p").build();
-    expect(storage.get(updatedInfo.name())).andReturn(updatedInfo);
+    Bucket expectedUpdatedBucket =
+        new Bucket(serviceMockReturnsOptions, new BucketInfo.BuilderImpl(updatedInfo));
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.get(updatedInfo.name())).andReturn(expectedUpdatedBucket);
     replay(storage);
+    initializeBucket();
     Bucket updatedBucket = bucket.reload();
-    assertSame(storage, updatedBucket.storage());
-    assertEquals(updatedInfo, updatedBucket.info());
+    assertEquals(expectedUpdatedBucket, updatedBucket);
   }
 
   @Test
   public void testReloadNull() throws Exception {
+    initializeExpectedBucket(4);
+    expect(storage.options()).andReturn(mockOptions);
     expect(storage.get(BUCKET_INFO.name())).andReturn(null);
     replay(storage);
+    initializeBucket();
     assertNull(bucket.reload());
   }
 
   @Test
   public void testReloadWithOptions() throws Exception {
+    initializeExpectedBucket(5);
     BucketInfo updatedInfo = BUCKET_INFO.toBuilder().notFoundPage("p").build();
+    Bucket expectedUpdatedBucket =
+        new Bucket(serviceMockReturnsOptions, new BucketInfo.BuilderImpl(updatedInfo));
+    expect(storage.options()).andReturn(mockOptions);
     expect(storage.get(updatedInfo.name(), Storage.BucketGetOption.metagenerationMatch(42L)))
-        .andReturn(updatedInfo);
+        .andReturn(expectedUpdatedBucket);
     replay(storage);
+    initializeBucket();
     Bucket updatedBucket = bucket.reload(Bucket.BucketSourceOption.metagenerationMatch());
-    assertSame(storage, updatedBucket.storage());
-    assertEquals(updatedInfo, updatedBucket.info());
+    assertEquals(expectedUpdatedBucket, updatedBucket);
   }
 
   @Test
   public void testUpdate() throws Exception {
-    BucketInfo updatedInfo = BUCKET_INFO.toBuilder().notFoundPage("p").build();
-    expect(storage.update(updatedInfo)).andReturn(updatedInfo);
+    initializeExpectedBucket(5);
+    Bucket expectedUpdatedBucket = expectedBucket.toBuilder().notFoundPage("p").build();
+    expect(storage.options()).andReturn(mockOptions).times(2);
+    expect(storage.update(expectedUpdatedBucket)).andReturn(expectedUpdatedBucket);
     replay(storage);
-    Bucket updatedBucket = bucket.update(updatedInfo);
-    assertSame(storage, bucket.storage());
-    assertEquals(updatedInfo, updatedBucket.info());
+    initializeBucket();
+    Bucket updatedBucket = new Bucket(storage, new BucketInfo.BuilderImpl(expectedUpdatedBucket));
+    Bucket actualUpdatedBucket = updatedBucket.update();
+    assertEquals(expectedUpdatedBucket, actualUpdatedBucket);
   }
 
   @Test
   public void testDelete() throws Exception {
+    initializeExpectedBucket(4);
+    expect(storage.options()).andReturn(mockOptions);
     expect(storage.delete(BUCKET_INFO.name())).andReturn(true);
     replay(storage);
+    initializeBucket();
     assertTrue(bucket.delete());
   }
 
   @Test
   public void testList() throws Exception {
-    StorageOptions storageOptions = createStrictMock(StorageOptions.class);
-    PageImpl blobInfoPage = new PageImpl<>(null, "c", BLOB_INFO_RESULTS);
-    expect(storage.list(BUCKET_INFO.name())).andReturn(blobInfoPage);
-    expect(storage.options()).andReturn(storageOptions);
-    expect(storageOptions.service()).andReturn(storage);
-    replay(storage, storageOptions);
+    initializeExpectedBucket(4);
+    PageImpl expectedBlobPage = new PageImpl<>(null, "c", blobResults);
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.list(BUCKET_INFO.name())).andReturn(expectedBlobPage);
+    replay(storage);
+    initializeBucket();
     Page blobPage = bucket.list();
-    Iterator blobInfoIterator = blobInfoPage.values().iterator();
+    Iterator blobInfoIterator = blobPage.values().iterator();
     Iterator blobIterator = blobPage.values().iterator();
     while (blobInfoIterator.hasNext() && blobIterator.hasNext()) {
-      assertEquals(blobInfoIterator.next(), blobIterator.next().info());
+      assertEquals(blobInfoIterator.next(), blobIterator.next());
     }
     assertFalse(blobInfoIterator.hasNext());
     assertFalse(blobIterator.hasNext());
-    assertEquals(blobInfoPage.nextPageCursor(), blobPage.nextPageCursor());
-    verify(storageOptions);
+    assertEquals(expectedBlobPage.nextPageCursor(), blobPage.nextPageCursor());
   }
 
   @Test
   public void testGet() throws Exception {
-    BlobInfo info = BlobInfo.builder("b", "n").build();
-    expect(storage.get(BlobId.of(bucket.info().name(), "n"), new Storage.BlobGetOption[0]))
-        .andReturn(info);
+    initializeExpectedBucket(5);
+    Blob expectedBlob = new Blob(
+        serviceMockReturnsOptions, new BlobInfo.BuilderImpl(BlobInfo.builder("b", "n").build()));
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.get(BlobId.of(expectedBucket.name(), "n"), new Storage.BlobGetOption[0]))
+        .andReturn(expectedBlob);
     replay(storage);
+    initializeBucket();
     Blob blob = bucket.get("n");
-    assertEquals(info, blob.info());
+    assertEquals(expectedBlob, blob);
   }
 
   @Test
   public void testGetAll() throws Exception {
+    initializeExpectedBucket(4);
     Capture capturedBatchRequest = Capture.newInstance();
-    List> batchResultList = new LinkedList<>();
-    for (BlobInfo info : BLOB_INFO_RESULTS) {
+    List> batchResultList = new LinkedList<>();
+    for (Blob info : blobResults) {
       batchResultList.add(new Result<>(info));
     }
     BatchResponse response = new BatchResponse(Collections.>emptyList(),
-        Collections.>emptyList(), batchResultList);
+        Collections.>emptyList(), batchResultList);
+    expect(storage.options()).andReturn(mockOptions);
     expect(storage.submit(capture(capturedBatchRequest))).andReturn(response);
+    expect(storage.options()).andReturn(mockOptions).times(3);
     replay(storage);
+    initializeBucket();
     List blobs = bucket.get("n1", "n2", "n3");
     Set blobInfoSet = capturedBatchRequest.getValue().toGet().keySet();
     assertEquals(batchResultList.size(), blobInfoSet.size());
-    for (BlobInfo info : BLOB_INFO_RESULTS) {
+    for (BlobInfo info : blobResults) {
       assertTrue(blobInfoSet.contains(info.blobId()));
     }
     Iterator blobIterator = blobs.iterator();
-    Iterator> batchResultIterator = response.gets().iterator();
+    Iterator> batchResultIterator = response.gets().iterator();
     while (batchResultIterator.hasNext() && blobIterator.hasNext()) {
-      assertEquals(batchResultIterator.next().get(), blobIterator.next().info());
+      assertEquals(batchResultIterator.next().get(), blobIterator.next());
     }
     assertFalse(batchResultIterator.hasNext());
     assertFalse(blobIterator.hasNext());
@@ -194,70 +275,111 @@ public void testGetAll() throws Exception {
 
   @Test
   public void testCreate() throws Exception {
+    initializeExpectedBucket(5);
     BlobInfo info = BlobInfo.builder("b", "n").contentType(CONTENT_TYPE).build();
+    Blob expectedBlob = new Blob(serviceMockReturnsOptions, new BlobInfo.BuilderImpl(info));
     byte[] content = {0xD, 0xE, 0xA, 0xD};
-    expect(storage.create(info, content)).andReturn(info);
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.create(info, content)).andReturn(expectedBlob);
     replay(storage);
+    initializeBucket();
     Blob blob = bucket.create("n", content, CONTENT_TYPE);
-    assertEquals(info, blob.info());
+    assertEquals(expectedBlob, blob);
   }
 
   @Test
   public void testCreateNullContentType() throws Exception {
+    initializeExpectedBucket(5);
     BlobInfo info = BlobInfo.builder("b", "n").contentType(Storage.DEFAULT_CONTENT_TYPE).build();
+    Blob expectedBlob = new Blob(serviceMockReturnsOptions, new BlobInfo.BuilderImpl(info));
     byte[] content = {0xD, 0xE, 0xA, 0xD};
-    expect(storage.create(info, content)).andReturn(info);
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.create(info, content)).andReturn(expectedBlob);
     replay(storage);
+    initializeBucket();
     Blob blob = bucket.create("n", content, null);
-    assertEquals(info, blob.info());
+    assertEquals(expectedBlob, blob);
   }
 
   @Test
   public void testCreateFromStream() throws Exception {
+    initializeExpectedBucket(5);
     BlobInfo info = BlobInfo.builder("b", "n").contentType(CONTENT_TYPE).build();
+    Blob expectedBlob = new Blob(serviceMockReturnsOptions, new BlobInfo.BuilderImpl(info));
     byte[] content = {0xD, 0xE, 0xA, 0xD};
     InputStream streamContent = new ByteArrayInputStream(content);
-    expect(storage.create(info, streamContent)).andReturn(info);
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.create(info, streamContent)).andReturn(expectedBlob);
     replay(storage);
+    initializeBucket();
     Blob blob = bucket.create("n", streamContent, CONTENT_TYPE);
-    assertEquals(info, blob.info());
+    assertEquals(expectedBlob, blob);
   }
 
   @Test
   public void testCreateFromStreamNullContentType() throws Exception {
+    initializeExpectedBucket(5);
     BlobInfo info = BlobInfo.builder("b", "n").contentType(Storage.DEFAULT_CONTENT_TYPE).build();
+    Blob expectedBlob = new Blob(serviceMockReturnsOptions, new BlobInfo.BuilderImpl(info));
     byte[] content = {0xD, 0xE, 0xA, 0xD};
     InputStream streamContent = new ByteArrayInputStream(content);
-    expect(storage.create(info, streamContent)).andReturn(info);
+    expect(storage.options()).andReturn(mockOptions);
+    expect(storage.create(info, streamContent)).andReturn(expectedBlob);
     replay(storage);
+    initializeBucket();
     Blob blob = bucket.create("n", streamContent, null);
-    assertEquals(info, blob.info());
+    assertEquals(expectedBlob, blob);
   }
 
   @Test
-  public void testStaticGet() throws Exception {
-    expect(storage.get(BUCKET_INFO.name())).andReturn(BUCKET_INFO);
-    replay(storage);
-    Bucket loadedBucket = Bucket.get(storage, BUCKET_INFO.name());
-    assertNotNull(loadedBucket);
-    assertEquals(BUCKET_INFO, loadedBucket.info());
-  }
-
-  @Test
-  public void testStaticGetNull() throws Exception {
-    expect(storage.get(BUCKET_INFO.name())).andReturn(null);
+  public void testToBuilder() {
+    expect(storage.options()).andReturn(mockOptions).times(4);
     replay(storage);
-    assertNull(Bucket.get(storage, BUCKET_INFO.name()));
+    Bucket fullBucket = new Bucket(storage, new BucketInfo.BuilderImpl(FULL_BUCKET_INFO));
+    assertEquals(fullBucket, fullBucket.toBuilder().build());
+    Bucket simpleBlob = new Bucket(storage, new BucketInfo.BuilderImpl(BUCKET_INFO));
+    assertEquals(simpleBlob, simpleBlob.toBuilder().build());
   }
 
   @Test
-  public void testStaticGetWithOptions() throws Exception {
-    expect(storage.get(BUCKET_INFO.name(), Storage.BucketGetOption.fields()))
-        .andReturn(BUCKET_INFO);
+  public void testBuilder() {
+    initializeExpectedBucket(4);
+    expect(storage.options()).andReturn(mockOptions).times(4);
     replay(storage);
-    Bucket loadedBucket =
-        Bucket.get(storage, BUCKET_INFO.name(), Storage.BucketGetOption.fields());
-    assertNotNull(loadedBucket);
-    assertEquals(BUCKET_INFO, loadedBucket.info());
+    Bucket.Builder builder =
+        new Bucket.Builder(new Bucket(storage, new BucketInfo.BuilderImpl(BUCKET_INFO)));
+    Bucket bucket = builder.acl(ACL)
+        .etag(ETAG)
+        .id(ID)
+        .metageneration(META_GENERATION)
+        .owner(OWNER)
+        .selfLink(SELF_LINK)
+        .cors(CORS)
+        .createTime(CREATE_TIME)
+        .defaultAcl(DEFAULT_ACL)
+        .deleteRules(DELETE_RULES)
+        .indexPage(INDEX_PAGE)
+        .notFoundPage(NOT_FOUND_PAGE)
+        .location(LOCATION)
+        .storageClass(STORAGE_CLASS)
+        .versioningEnabled(VERSIONING_ENABLED)
+        .build();
+    assertEquals("b", bucket.name());
+    assertEquals(ACL, bucket.acl());
+    assertEquals(ETAG, bucket.etag());
+    assertEquals(ID, bucket.id());
+    assertEquals(META_GENERATION, bucket.metageneration());
+    assertEquals(OWNER, bucket.owner());
+    assertEquals(SELF_LINK, bucket.selfLink());
+    assertEquals(CREATE_TIME, bucket.createTime());
+    assertEquals(CORS, bucket.cors());
+    assertEquals(DEFAULT_ACL, bucket.defaultAcl());
+    assertEquals(DELETE_RULES, bucket.deleteRules());
+    assertEquals(INDEX_PAGE, bucket.indexPage());
+    assertEquals(NOT_FOUND_PAGE, bucket.notFoundPage());
+    assertEquals(LOCATION, bucket.location());
+    assertEquals(STORAGE_CLASS, bucket.storageClass());
+    assertEquals(VERSIONING_ENABLED, bucket.versioningEnabled());
+    assertEquals(storage.options(), bucket.storage().options());
   }
 }
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 63b9d739b686..dffcc366036a 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
@@ -86,7 +86,8 @@ public static void afterClass() throws ExecutionException, InterruptedException
 
   @Test(timeout = 5000)
   public void testListBuckets() throws InterruptedException {
-    Iterator bucketIterator = storage.list(Storage.BucketListOption.prefix(BUCKET),
+    Iterator bucketIterator =
+        storage.list(Storage.BucketListOption.prefix(BUCKET),
         Storage.BucketListOption.fields()).values().iterator();
     while (!bucketIterator.hasNext()) {
       Thread.sleep(500);
@@ -94,7 +95,7 @@ public void testListBuckets() throws InterruptedException {
           Storage.BucketListOption.fields()).values().iterator();
     }
     while (bucketIterator.hasNext()) {
-      BucketInfo remoteBucket = bucketIterator.next();
+      Bucket remoteBucket = bucketIterator.next();
       assertTrue(remoteBucket.name().startsWith(BUCKET));
       assertNull(remoteBucket.createTime());
       assertNull(remoteBucket.selfLink());
@@ -103,7 +104,7 @@ public void testListBuckets() throws InterruptedException {
 
   @Test
   public void testGetBucketSelectedFields() {
-    BucketInfo remoteBucket = storage.get(BUCKET, Storage.BucketGetOption.fields(BucketField.ID));
+    Bucket remoteBucket = storage.get(BUCKET, Storage.BucketGetOption.fields(BucketField.ID));
     assertEquals(BUCKET, remoteBucket.name());
     assertNull(remoteBucket.createTime());
     assertNotNull(remoteBucket.id());
@@ -111,7 +112,7 @@ public void testGetBucketSelectedFields() {
 
   @Test
   public void testGetBucketAllSelectedFields() {
-    BucketInfo remoteBucket = storage.get(BUCKET,
+    Bucket remoteBucket = storage.get(BUCKET,
         Storage.BucketGetOption.fields(BucketField.values()));
     assertEquals(BUCKET, remoteBucket.name());
     assertNotNull(remoteBucket.createTime());
@@ -120,7 +121,7 @@ public void testGetBucketAllSelectedFields() {
 
   @Test
   public void testGetBucketEmptyFields() {
-    BucketInfo remoteBucket = storage.get(BUCKET, Storage.BucketGetOption.fields());
+    Bucket remoteBucket = storage.get(BUCKET, Storage.BucketGetOption.fields());
     assertEquals(BUCKET, remoteBucket.name());
     assertNull(remoteBucket.createTime());
     assertNull(remoteBucket.selfLink());
@@ -130,26 +131,26 @@ public void testGetBucketEmptyFields() {
   public void testCreateBlob() {
     String blobName = "test-create-blob";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
-    BlobInfo remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT);
+    Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT);
     assertNotNull(remoteBlob);
     assertEquals(blob.bucket(), remoteBlob.bucket());
     assertEquals(blob.name(), remoteBlob.name());
     byte[] readBytes = storage.readAllBytes(BUCKET, blobName);
     assertArrayEquals(BLOB_BYTE_CONTENT, readBytes);
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
   public void testCreateEmptyBlob() {
     String blobName = "test-create-empty-blob";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
-    BlobInfo remoteBlob = storage.create(blob);
+    Blob remoteBlob = storage.create(blob);
     assertNotNull(remoteBlob);
     assertEquals(blob.bucket(), remoteBlob.bucket());
     assertEquals(blob.name(), remoteBlob.name());
     byte[] readBytes = storage.readAllBytes(BUCKET, blobName);
     assertArrayEquals(new byte[0], readBytes);
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
@@ -157,21 +158,22 @@ public void testCreateBlobStream() {
     String blobName = "test-create-blob-stream";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).contentType(CONTENT_TYPE).build();
     ByteArrayInputStream stream = new ByteArrayInputStream(BLOB_STRING_CONTENT.getBytes(UTF_8));
-    BlobInfo remoteBlob = storage.create(blob, stream);
+    Blob remoteBlob = storage.create(blob, stream);
     assertNotNull(remoteBlob);
     assertEquals(blob.bucket(), remoteBlob.bucket());
     assertEquals(blob.name(), remoteBlob.name());
     assertEquals(blob.contentType(), remoteBlob.contentType());
     byte[] readBytes = storage.readAllBytes(BUCKET, blobName);
     assertEquals(BLOB_STRING_CONTENT, new String(readBytes, UTF_8));
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
   public void testCreateBlobFail() {
     String blobName = "test-create-blob-fail";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
-    assertNotNull(storage.create(blob));
+    Blob remoteBlob = storage.create(blob);
+    assertNotNull(remoteBlob);
     BlobInfo wrongGenerationBlob = BlobInfo.builder(BUCKET, blobName, -1L).build();
     try {
       storage.create(wrongGenerationBlob, BLOB_BYTE_CONTENT,
@@ -180,7 +182,7 @@ public void testCreateBlobFail() {
     } catch (StorageException ex) {
       // expected
     }
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
@@ -204,10 +206,10 @@ public void testGetBlobEmptySelectedFields() {
     String blobName = "test-get-empty-selected-fields-blob";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).contentType(CONTENT_TYPE).build();
     assertNotNull(storage.create(blob));
-    BlobInfo remoteBlob = storage.get(blob.blobId(), Storage.BlobGetOption.fields());
+    Blob remoteBlob = storage.get(blob.blobId(), Storage.BlobGetOption.fields());
     assertEquals(blob.blobId(), remoteBlob.blobId());
     assertNull(remoteBlob.contentType());
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
@@ -218,12 +220,12 @@ public void testGetBlobSelectedFields() {
         .metadata(ImmutableMap.of("k", "v"))
         .build();
     assertNotNull(storage.create(blob));
-    BlobInfo remoteBlob = storage.get(blob.blobId(), Storage.BlobGetOption.fields(
+    Blob remoteBlob = storage.get(blob.blobId(), Storage.BlobGetOption.fields(
         BlobField.METADATA));
     assertEquals(blob.blobId(), remoteBlob.blobId());
     assertEquals(ImmutableMap.of("k", "v"), remoteBlob.metadata());
     assertNull(remoteBlob.contentType());
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
@@ -234,21 +236,22 @@ public void testGetBlobAllSelectedFields() {
         .metadata(ImmutableMap.of("k", "v"))
         .build();
     assertNotNull(storage.create(blob));
-    BlobInfo remoteBlob = storage.get(blob.blobId(),
+    Blob remoteBlob = storage.get(blob.blobId(),
         Storage.BlobGetOption.fields(BlobField.values()));
     assertEquals(blob.bucket(), remoteBlob.bucket());
     assertEquals(blob.name(), remoteBlob.name());
     assertEquals(ImmutableMap.of("k", "v"), remoteBlob.metadata());
     assertNotNull(remoteBlob.id());
     assertNotNull(remoteBlob.selfLink());
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
   public void testGetBlobFail() {
     String blobName = "test-get-blob-fail";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
-    assertNotNull(storage.create(blob));
+    Blob remoteBlob = storage.create(blob);
+    assertNotNull(remoteBlob);
     BlobId wrongGenerationBlob = BlobId.of(BUCKET, blobName);
     try {
       storage.get(wrongGenerationBlob, Storage.BlobGetOption.generationMatch(-1));
@@ -256,17 +259,18 @@ public void testGetBlobFail() {
     } catch (StorageException ex) {
       // expected
     }
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
   public void testGetBlobFailNonExistingGeneration() {
     String blobName = "test-get-blob-fail-non-existing-generation";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
-    assertNotNull(storage.create(blob));
+    Blob remoteBlob = storage.create(blob);
+    assertNotNull(remoteBlob);
     BlobId wrongGenerationBlob = BlobId.of(BUCKET, blobName, -1L);
     assertNull(storage.get(wrongGenerationBlob));
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
@@ -282,20 +286,23 @@ public void testListBlobsSelectedFields() {
         .contentType(CONTENT_TYPE)
         .metadata(metadata)
         .build();
-    assertNotNull(storage.create(blob1));
-    assertNotNull(storage.create(blob2));
-    Page page = storage.list(BUCKET,
+    Blob remoteBlob1 = storage.create(blob1);
+    Blob remoteBlob2 = storage.create(blob2);
+    assertNotNull(remoteBlob1);
+    assertNotNull(remoteBlob2);
+    Page page =
+        storage.list(BUCKET,
         Storage.BlobListOption.prefix("test-list-blobs-selected-fields-blob"),
         Storage.BlobListOption.fields(BlobField.METADATA));
     int index = 0;
-    for (BlobInfo remoteBlob : page.values()) {
+    for (Blob remoteBlob : page.values()) {
       assertEquals(BUCKET, remoteBlob.bucket());
       assertEquals(blobNames[index++], remoteBlob.name());
       assertEquals(metadata, remoteBlob.metadata());
       assertNull(remoteBlob.contentType());
     }
-    assertTrue(storage.delete(BUCKET, blobNames[0]));
-    assertTrue(storage.delete(BUCKET, blobNames[1]));
+    assertTrue(remoteBlob1.delete());
+    assertTrue(remoteBlob2.delete());
   }
 
   @Test
@@ -308,32 +315,36 @@ public void testListBlobsEmptySelectedFields() {
     BlobInfo blob2 = BlobInfo.builder(BUCKET, blobNames[1])
         .contentType(CONTENT_TYPE)
         .build();
-    assertNotNull(storage.create(blob1));
-    assertNotNull(storage.create(blob2));
-    Page page = storage.list(BUCKET,
+    Blob remoteBlob1 = storage.create(blob1);
+    Blob remoteBlob2 = storage.create(blob2);
+    assertNotNull(remoteBlob1);
+    assertNotNull(remoteBlob2);
+    Page page = storage.list(
+        BUCKET,
         Storage.BlobListOption.prefix("test-list-blobs-empty-selected-fields-blob"),
         Storage.BlobListOption.fields());
     int index = 0;
-    for (BlobInfo remoteBlob : page.values()) {
+    for (Blob remoteBlob : page.values()) {
       assertEquals(BUCKET, remoteBlob.bucket());
       assertEquals(blobNames[index++], remoteBlob.name());
       assertNull(remoteBlob.contentType());
     }
-    assertTrue(storage.delete(BUCKET, blobNames[0]));
-    assertTrue(storage.delete(BUCKET, blobNames[1]));
+    assertTrue(remoteBlob1.delete());
+    assertTrue(remoteBlob2.delete());
   }
 
   @Test
   public void testUpdateBlob() {
     String blobName = "test-update-blob";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
-    assertNotNull(storage.create(blob));
-    BlobInfo updatedBlob = storage.update(blob.toBuilder().contentType(CONTENT_TYPE).build());
+    Blob remoteBlob = storage.create(blob);
+    assertNotNull(remoteBlob);
+    Blob updatedBlob = remoteBlob.toBuilder().contentType(CONTENT_TYPE).build().update();
     assertNotNull(updatedBlob);
     assertEquals(blob.name(), updatedBlob.name());
     assertEquals(blob.bucket(), updatedBlob.bucket());
     assertEquals(CONTENT_TYPE, updatedBlob.contentType());
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(updatedBlob.delete());
   }
 
   @Test
@@ -345,15 +356,16 @@ public void testUpdateBlobReplaceMetadata() {
         .contentType(CONTENT_TYPE)
         .metadata(metadata)
         .build();
-    assertNotNull(storage.create(blob));
-    BlobInfo updatedBlob = storage.update(blob.toBuilder().metadata(null).build());
+    Blob remoteBlob = storage.create(blob);
+    assertNotNull(remoteBlob);
+    Blob updatedBlob = remoteBlob.toBuilder().metadata(null).build().update();
     assertNotNull(updatedBlob);
     assertNull(updatedBlob.metadata());
-    updatedBlob = storage.update(blob.toBuilder().metadata(newMetadata).build());
+    updatedBlob = remoteBlob.toBuilder().metadata(newMetadata).build().update();
     assertEquals(blob.name(), updatedBlob.name());
     assertEquals(blob.bucket(), updatedBlob.bucket());
     assertEquals(newMetadata, updatedBlob.metadata());
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(updatedBlob.delete());
   }
 
   @Test
@@ -366,13 +378,14 @@ public void testUpdateBlobMergeMetadata() {
         .contentType(CONTENT_TYPE)
         .metadata(metadata)
         .build();
-    assertNotNull(storage.create(blob));
-    BlobInfo updatedBlob = storage.update(blob.toBuilder().metadata(newMetadata).build());
+    Blob remoteBlob = storage.create(blob);
+    assertNotNull(remoteBlob);
+    Blob updatedBlob = remoteBlob.toBuilder().metadata(newMetadata).build().update();
     assertNotNull(updatedBlob);
     assertEquals(blob.name(), updatedBlob.name());
     assertEquals(blob.bucket(), updatedBlob.bucket());
     assertEquals(expectedMetadata, updatedBlob.metadata());
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(updatedBlob.delete());
   }
 
   @Test
@@ -387,20 +400,22 @@ public void testUpdateBlobUnsetMetadata() {
         .contentType(CONTENT_TYPE)
         .metadata(metadata)
         .build();
-    assertNotNull(storage.create(blob));
-    BlobInfo updatedBlob = storage.update(blob.toBuilder().metadata(newMetadata).build());
+    Blob remoteBlob = storage.create(blob);
+    assertNotNull(remoteBlob);
+    Blob updatedBlob = remoteBlob.toBuilder().metadata(newMetadata).build().update();
     assertNotNull(updatedBlob);
     assertEquals(blob.name(), updatedBlob.name());
     assertEquals(blob.bucket(), updatedBlob.bucket());
     assertEquals(expectedMetadata, updatedBlob.metadata());
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(updatedBlob.delete());
   }
 
   @Test
   public void testUpdateBlobFail() {
     String blobName = "test-update-blob-fail";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
-    assertNotNull(storage.create(blob));
+    Blob remoteBlob = storage.create(blob);
+    assertNotNull(remoteBlob);
     BlobInfo wrongGenerationBlob = BlobInfo.builder(BUCKET, blobName, -1L)
         .contentType(CONTENT_TYPE)
         .build();
@@ -410,7 +425,7 @@ public void testUpdateBlobFail() {
     } catch (StorageException ex) {
       // expected
     }
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
@@ -431,14 +446,15 @@ public void testDeleteBlobNonExistingGeneration() {
   public void testDeleteBlobFail() {
     String blobName = "test-delete-blob-fail";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
-    assertNotNull(storage.create(blob));
+    Blob remoteBlob = storage.create(blob);
+    assertNotNull(remoteBlob);
     try {
       storage.delete(BUCKET, blob.name(), Storage.BlobSourceOption.generationMatch(-1L));
       fail("StorageException was expected");
     } catch (StorageException ex) {
       // expected
     }
-    assertTrue(storage.delete(BUCKET, blob.name()));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
@@ -447,24 +463,26 @@ public void testComposeBlob() {
     String sourceBlobName2 = "test-compose-blob-source-2";
     BlobInfo sourceBlob1 = BlobInfo.builder(BUCKET, sourceBlobName1).build();
     BlobInfo sourceBlob2 = BlobInfo.builder(BUCKET, sourceBlobName2).build();
-    assertNotNull(storage.create(sourceBlob1, BLOB_BYTE_CONTENT));
-    assertNotNull(storage.create(sourceBlob2, BLOB_BYTE_CONTENT));
+    Blob remoteSourceBlob1 = storage.create(sourceBlob1, BLOB_BYTE_CONTENT);
+    Blob remoteSourceBlob2 = storage.create(sourceBlob2, BLOB_BYTE_CONTENT);
+    assertNotNull(remoteSourceBlob1);
+    assertNotNull(remoteSourceBlob2);
     String targetBlobName = "test-compose-blob-target";
     BlobInfo targetBlob = BlobInfo.builder(BUCKET, targetBlobName).build();
     Storage.ComposeRequest req =
         Storage.ComposeRequest.of(ImmutableList.of(sourceBlobName1, sourceBlobName2), targetBlob);
-    BlobInfo remoteBlob = storage.compose(req);
-    assertNotNull(remoteBlob);
-    assertEquals(targetBlob.name(), remoteBlob.name());
-    assertEquals(targetBlob.bucket(), remoteBlob.bucket());
+    Blob remoteTargetBlob = storage.compose(req);
+    assertNotNull(remoteTargetBlob);
+    assertEquals(targetBlob.name(), remoteTargetBlob.name());
+    assertEquals(targetBlob.bucket(), remoteTargetBlob.bucket());
     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,
         BLOB_BYTE_CONTENT.length);
     assertArrayEquals(composedBytes, readBytes);
-    assertTrue(storage.delete(BUCKET, sourceBlobName1));
-    assertTrue(storage.delete(BUCKET, sourceBlobName2));
-    assertTrue(storage.delete(BUCKET, targetBlobName));
+    assertTrue(remoteSourceBlob1.delete());
+    assertTrue(remoteSourceBlob2.delete());
+    assertTrue(remoteTargetBlob.delete());
   }
 
   @Test
@@ -473,8 +491,10 @@ public void testComposeBlobFail() {
     String sourceBlobName2 = "test-compose-blob-fail-source-2";
     BlobInfo sourceBlob1 = BlobInfo.builder(BUCKET, sourceBlobName1).build();
     BlobInfo sourceBlob2 = BlobInfo.builder(BUCKET, sourceBlobName2).build();
-    assertNotNull(storage.create(sourceBlob1));
-    assertNotNull(storage.create(sourceBlob2));
+    Blob remoteSourceBlob1 = storage.create(sourceBlob1);
+    Blob remoteSourceBlob2 = storage.create(sourceBlob2);
+    assertNotNull(remoteSourceBlob1);
+    assertNotNull(remoteSourceBlob2);
     String targetBlobName = "test-compose-blob-fail-target";
     BlobInfo targetBlob = BlobInfo.builder(BUCKET, targetBlobName).build();
     Storage.ComposeRequest req = Storage.ComposeRequest.builder()
@@ -488,8 +508,8 @@ public void testComposeBlobFail() {
     } catch (StorageException ex) {
       // expected
     }
-    assertTrue(storage.delete(BUCKET, sourceBlobName1));
-    assertTrue(storage.delete(BUCKET, sourceBlobName2));
+    assertTrue(remoteSourceBlob1.delete());
+    assertTrue(remoteSourceBlob2.delete());
   }
 
   @Test
@@ -501,7 +521,8 @@ public void testCopyBlob() {
         .contentType(CONTENT_TYPE)
         .metadata(metadata)
         .build();
-    assertNotNull(storage.create(blob, BLOB_BYTE_CONTENT));
+    Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT);
+    assertNotNull(remoteBlob);
     String targetBlobName = "test-copy-blob-target";
     Storage.CopyRequest req = Storage.CopyRequest.of(source, BlobId.of(BUCKET, targetBlobName));
     CopyWriter copyWriter = storage.copy(req);
@@ -510,7 +531,7 @@ public void testCopyBlob() {
     assertEquals(CONTENT_TYPE, copyWriter.result().contentType());
     assertEquals(metadata, copyWriter.result().metadata());
     assertTrue(copyWriter.isDone());
-    assertTrue(storage.delete(BUCKET, sourceBlobName));
+    assertTrue(remoteBlob.delete());
     assertTrue(storage.delete(BUCKET, targetBlobName));
   }
 
@@ -518,7 +539,8 @@ public void testCopyBlob() {
   public void testCopyBlobUpdateMetadata() {
     String sourceBlobName = "test-copy-blob-update-metadata-source";
     BlobId source = BlobId.of(BUCKET, sourceBlobName);
-    assertNotNull(storage.create(BlobInfo.builder(source).build(), BLOB_BYTE_CONTENT));
+    Blob remoteSourceBlob = storage.create(BlobInfo.builder(source).build(), BLOB_BYTE_CONTENT);
+    assertNotNull(remoteSourceBlob);
     String targetBlobName = "test-copy-blob-update-metadata-target";
     ImmutableMap metadata = ImmutableMap.of("k", "v");
     BlobInfo target = BlobInfo.builder(BUCKET, targetBlobName)
@@ -532,7 +554,7 @@ public void testCopyBlobUpdateMetadata() {
     assertEquals(CONTENT_TYPE, copyWriter.result().contentType());
     assertEquals(metadata, copyWriter.result().metadata());
     assertTrue(copyWriter.isDone());
-    assertTrue(storage.delete(BUCKET, sourceBlobName));
+    assertTrue(remoteSourceBlob.delete());
     assertTrue(storage.delete(BUCKET, targetBlobName));
   }
 
@@ -540,7 +562,8 @@ public void testCopyBlobUpdateMetadata() {
   public void testCopyBlobFail() {
     String sourceBlobName = "test-copy-blob-source-fail";
     BlobId source = BlobId.of(BUCKET, sourceBlobName, -1L);
-    assertNotNull(storage.create(BlobInfo.builder(source).build(), BLOB_BYTE_CONTENT));
+    Blob remoteSourceBlob = storage.create(BlobInfo.builder(source).build(), BLOB_BYTE_CONTENT);
+    assertNotNull(remoteSourceBlob);
     String targetBlobName = "test-copy-blob-target-fail";
     BlobInfo target = BlobInfo.builder(BUCKET, targetBlobName).contentType(CONTENT_TYPE).build();
     Storage.CopyRequest req = Storage.CopyRequest.builder()
@@ -565,7 +588,7 @@ public void testCopyBlobFail() {
     } catch (StorageException ex) {
       // expected
     }
-    assertTrue(storage.delete(BUCKET, sourceBlobName));
+    assertTrue(remoteSourceBlob.delete());
   }
 
   @Test
@@ -658,25 +681,26 @@ public void testBatchRequestManyDeletes() {
     }
 
     // Check updates
-    BlobInfo remoteUpdatedBlob2 = response.updates().get(0).get();
+    Blob remoteUpdatedBlob2 = response.updates().get(0).get();
     assertEquals(sourceBlob2.bucket(), remoteUpdatedBlob2.bucket());
     assertEquals(sourceBlob2.name(), remoteUpdatedBlob2.name());
     assertEquals(updatedBlob2.contentType(), remoteUpdatedBlob2.contentType());
 
     // Check gets
-    BlobInfo remoteBlob1 = response.gets().get(0).get();
+    Blob remoteBlob1 = response.gets().get(0).get();
     assertEquals(sourceBlob1.bucket(), remoteBlob1.bucket());
     assertEquals(sourceBlob1.name(), remoteBlob1.name());
 
-    assertTrue(storage.delete(BUCKET, sourceBlobName1));
-    assertTrue(storage.delete(BUCKET, sourceBlobName2));
+    assertTrue(remoteBlob1.delete());
+    assertTrue(remoteUpdatedBlob2.delete());
   }
 
   @Test
   public void testBatchRequestFail() {
     String blobName = "test-batch-request-blob-fail";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
-    assertNotNull(storage.create(blob));
+    Blob remoteBlob = storage.create(blob);
+    assertNotNull(remoteBlob);
     BlobInfo updatedBlob = BlobInfo.builder(BUCKET, blobName, -1L).build();
     BatchRequest batchRequest = BatchRequest.builder()
         .update(updatedBlob, Storage.BlobTargetOption.generationMatch())
@@ -696,7 +720,7 @@ public void testBatchRequestFail() {
     assertTrue(batchResponse.deletes().get(0).failed());
     assertFalse(batchResponse.deletes().get(1).failed());
     assertFalse(batchResponse.deletes().get(1).get());
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
@@ -755,7 +779,8 @@ public void testReadAndWriteCaptureChannels() throws IOException {
   public void testReadChannelFail() throws IOException {
     String blobName = "test-read-channel-blob-fail";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
-    assertNotNull(storage.create(blob));
+    Blob remoteBlob = storage.create(blob);
+    assertNotNull(remoteBlob);
     try (ReadChannel reader =
         storage.reader(blob.blobId(), Storage.BlobSourceOption.metagenerationMatch(-1L))) {
       reader.read(ByteBuffer.allocate(42));
@@ -778,7 +803,7 @@ public void testReadChannelFail() throws IOException {
     } catch (StorageException ex) {
       // expected
     }
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
@@ -790,7 +815,7 @@ public void testReadChannelFailUpdatedGeneration() throws IOException {
     int blobSize = 2 * chunkSize;
     byte[] content = new byte[blobSize];
     random.nextBytes(content);
-    BlobInfo remoteBlob = storage.create(blob, content);
+    Blob remoteBlob = storage.create(blob, content);
     assertNotNull(remoteBlob);
     assertEquals(blobSize, (long) remoteBlob.size());
     try (ReadChannel reader = storage.reader(blob.blobId())) {
@@ -834,9 +859,9 @@ public void testWriteChannelFail() throws IOException {
   public void testWriteChannelExistingBlob() throws IOException {
     String blobName = "test-write-channel-existing-blob";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
-    BlobInfo remoteBlob = storage.create(blob);
+    storage.create(blob);
     byte[] stringBytes;
-    try (WriteChannel writer = storage.writer(remoteBlob)) {
+    try (WriteChannel writer = storage.writer(blob)) {
       stringBytes = BLOB_STRING_CONTENT.getBytes(UTF_8);
       writer.write(ByteBuffer.wrap(stringBytes));
     }
@@ -848,14 +873,15 @@ public void testWriteChannelExistingBlob() throws IOException {
   public void testGetSignedUrl() throws IOException {
     String blobName = "test-get-signed-url-blob";
     BlobInfo blob = BlobInfo.builder(BUCKET, blobName).build();
-    assertNotNull(storage.create(blob, BLOB_BYTE_CONTENT));
+    Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT);
+    assertNotNull(remoteBlob);
     URL url = storage.signUrl(blob, 1, TimeUnit.HOURS);
     URLConnection connection = url.openConnection();
     byte[] readBytes = new byte[BLOB_BYTE_CONTENT.length];
     try (InputStream responseStream = connection.getInputStream()) {
       assertEquals(BLOB_BYTE_CONTENT.length, responseStream.read(readBytes));
       assertArrayEquals(BLOB_BYTE_CONTENT, readBytes);
-      assertTrue(storage.delete(BUCKET, blobName));
+      assertTrue(remoteBlob.delete());
     }
   }
 
@@ -869,11 +895,11 @@ public void testPostSignedUrl() throws IOException {
     URLConnection connection = url.openConnection();
     connection.setDoOutput(true);
     connection.connect();
-    BlobInfo remoteBlob = storage.get(BUCKET, blobName);
+    Blob remoteBlob = storage.get(BUCKET, blobName);
     assertNotNull(remoteBlob);
     assertEquals(blob.bucket(), remoteBlob.bucket());
     assertEquals(blob.name(), remoteBlob.name());
-    assertTrue(storage.delete(BUCKET, blobName));
+    assertTrue(remoteBlob.delete());
   }
 
   @Test
@@ -884,13 +910,13 @@ public void testGetBlobs() {
     BlobInfo sourceBlob2 = BlobInfo.builder(BUCKET, sourceBlobName2).build();
     assertNotNull(storage.create(sourceBlob1));
     assertNotNull(storage.create(sourceBlob2));
-    List remoteBlobs = storage.get(sourceBlob1.blobId(), sourceBlob2.blobId());
+    List remoteBlobs = storage.get(sourceBlob1.blobId(), sourceBlob2.blobId());
     assertEquals(sourceBlob1.bucket(), remoteBlobs.get(0).bucket());
     assertEquals(sourceBlob1.name(), remoteBlobs.get(0).name());
     assertEquals(sourceBlob2.bucket(), remoteBlobs.get(1).bucket());
     assertEquals(sourceBlob2.name(), remoteBlobs.get(1).name());
-    assertTrue(storage.delete(BUCKET, sourceBlobName1));
-    assertTrue(storage.delete(BUCKET, sourceBlobName2));
+    assertTrue(remoteBlobs.get(0).delete());
+    assertTrue(remoteBlobs.get(1).delete());
   }
 
   @Test
@@ -900,11 +926,11 @@ public void testGetBlobsFail() {
     BlobInfo sourceBlob1 = BlobInfo.builder(BUCKET, sourceBlobName1).build();
     BlobInfo sourceBlob2 = BlobInfo.builder(BUCKET, sourceBlobName2).build();
     assertNotNull(storage.create(sourceBlob1));
-    List remoteBlobs = storage.get(sourceBlob1.blobId(), sourceBlob2.blobId());
+    List remoteBlobs = storage.get(sourceBlob1.blobId(), sourceBlob2.blobId());
     assertEquals(sourceBlob1.bucket(), remoteBlobs.get(0).bucket());
     assertEquals(sourceBlob1.name(), remoteBlobs.get(0).name());
     assertNull(remoteBlobs.get(1));
-    assertTrue(storage.delete(BUCKET, sourceBlobName1));
+    assertTrue(remoteBlobs.get(0).delete());
   }
 
   @Test
@@ -938,11 +964,11 @@ public void testUpdateBlobs() {
     String sourceBlobName2 = "test-update-blobs-2";
     BlobInfo sourceBlob1 = BlobInfo.builder(BUCKET, sourceBlobName1).build();
     BlobInfo sourceBlob2 = BlobInfo.builder(BUCKET, sourceBlobName2).build();
-    BlobInfo remoteBlob1 = storage.create(sourceBlob1);
-    BlobInfo remoteBlob2 = storage.create(sourceBlob2);
+    Blob remoteBlob1 = storage.create(sourceBlob1);
+    Blob remoteBlob2 = storage.create(sourceBlob2);
     assertNotNull(remoteBlob1);
     assertNotNull(remoteBlob2);
-    List updatedBlobs = storage.update(
+    List updatedBlobs = storage.update(
         remoteBlob1.toBuilder().contentType(CONTENT_TYPE).build(),
         remoteBlob2.toBuilder().contentType(CONTENT_TYPE).build());
     assertEquals(sourceBlob1.bucket(), updatedBlobs.get(0).bucket());
@@ -951,8 +977,8 @@ public void testUpdateBlobs() {
     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));
+    assertTrue(updatedBlobs.get(0).delete());
+    assertTrue(updatedBlobs.get(1).delete());
   }
 
   @Test
@@ -963,13 +989,13 @@ public void testUpdateBlobsFail() {
     BlobInfo sourceBlob2 = BlobInfo.builder(BUCKET, sourceBlobName2).build();
     BlobInfo remoteBlob1 = storage.create(sourceBlob1);
     assertNotNull(remoteBlob1);
-    List updatedBlobs = storage.update(
+    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));
+    assertTrue(updatedBlobs.get(0).delete());
   }
 }
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java
index d06f004fe84c..2f56bbda7bd9 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java
@@ -24,6 +24,7 @@
 import com.google.gcloud.storage.testing.RemoteGcsHelper;
 
 import org.easymock.EasyMock;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -66,42 +67,58 @@ public class RemoteGcsHelperTest {
       + "  \"type\": \"service_account\"\n"
       + "}";
   private static final InputStream JSON_KEY_STREAM = new ByteArrayInputStream(JSON_KEY.getBytes());
-  private static final List BLOB_LIST = ImmutableList.of(
-      BlobInfo.builder(BUCKET_NAME, "n1").build(),
-      BlobInfo.builder(BUCKET_NAME, "n2").build());
   private static final StorageException RETRYABLE_EXCEPTION = new StorageException(409, "");
   private static final StorageException FATAL_EXCEPTION = new StorageException(500, "");
-  private static final Page BLOB_PAGE = new Page() {
 
-    @Override
-    public String nextPageCursor() {
-      return "nextPageCursor";
-    }
-
-    @Override
-    public Page nextPage() {
-      return null;
-    }
-
-    @Override
-    public Iterable values() {
-      return BLOB_LIST;
-    }
-
-    @Override
-    public Iterator iterateAll() {
-      return BLOB_LIST.iterator();
-    }
-  };
+  private static Storage serviceMockReturnsOptions;
+  private List blobList;
+  private Page blobPage;
 
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
+  @Before
+  public void setUp() {
+    serviceMockReturnsOptions = EasyMock.createMock(Storage.class);
+    EasyMock.expect(serviceMockReturnsOptions.options())
+        .andReturn(EasyMock.createMock(StorageOptions.class))
+        .times(2);
+    EasyMock.replay(serviceMockReturnsOptions);
+    blobList = ImmutableList.of(
+        new Blob(
+            serviceMockReturnsOptions,
+            new BlobInfo.BuilderImpl(BlobInfo.builder(BUCKET_NAME, "n1").build())),
+        new Blob(
+            serviceMockReturnsOptions,
+            new BlobInfo.BuilderImpl(BlobInfo.builder(BUCKET_NAME, "n2").build())));
+    blobPage = new Page() {
+      @Override
+      public String nextPageCursor() {
+        return "nextPageCursor";
+      }
+
+      @Override
+      public Page nextPage() {
+        return null;
+      }
+
+      @Override
+      public Iterable values() {
+        return blobList;
+      }
+
+      @Override
+      public Iterator iterateAll() {
+        return blobList.iterator();
+      }
+    };
+  }
+
   @Test
   public void testForceDelete() throws InterruptedException, ExecutionException {
     Storage storageMock = EasyMock.createMock(Storage.class);
-    EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(BLOB_PAGE);
-    for (BlobInfo info : BLOB_LIST) {
+    EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(blobPage);
+    for (BlobInfo info : blobList) {
       EasyMock.expect(storageMock.delete(BUCKET_NAME, info.name())).andReturn(true);
     }
     EasyMock.expect(storageMock.delete(BUCKET_NAME)).andReturn(true);
@@ -113,8 +130,8 @@ public void testForceDelete() throws InterruptedException, ExecutionException {
   @Test
   public void testForceDeleteTimeout() throws InterruptedException, ExecutionException {
     Storage storageMock = EasyMock.createMock(Storage.class);
-    EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(BLOB_PAGE).anyTimes();
-    for (BlobInfo info : BLOB_LIST) {
+    EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(blobPage).anyTimes();
+    for (BlobInfo info : blobList) {
       EasyMock.expect(storageMock.delete(BUCKET_NAME, info.name())).andReturn(true).anyTimes();
     }
     EasyMock.expect(storageMock.delete(BUCKET_NAME)).andThrow(RETRYABLE_EXCEPTION).anyTimes();
@@ -126,8 +143,8 @@ public void testForceDeleteTimeout() throws InterruptedException, ExecutionExcep
   @Test
   public void testForceDeleteFail() throws InterruptedException, ExecutionException {
     Storage storageMock = EasyMock.createMock(Storage.class);
-    EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(BLOB_PAGE);
-    for (BlobInfo info : BLOB_LIST) {
+    EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(blobPage);
+    for (BlobInfo info : blobList) {
       EasyMock.expect(storageMock.delete(BUCKET_NAME, info.name())).andReturn(true);
     }
     EasyMock.expect(storageMock.delete(BUCKET_NAME)).andThrow(FATAL_EXCEPTION);
@@ -143,8 +160,8 @@ public void testForceDeleteFail() throws InterruptedException, ExecutionExceptio
   @Test
   public void testForceDeleteNoTimeout() {
     Storage storageMock = EasyMock.createMock(Storage.class);
-    EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(BLOB_PAGE);
-    for (BlobInfo info : BLOB_LIST) {
+    EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(blobPage);
+    for (BlobInfo info : blobList) {
       EasyMock.expect(storageMock.delete(BUCKET_NAME, info.name())).andReturn(true);
     }
     EasyMock.expect(storageMock.delete(BUCKET_NAME)).andReturn(true);
@@ -156,8 +173,8 @@ public void testForceDeleteNoTimeout() {
   @Test
   public void testForceDeleteNoTimeoutFail() {
     Storage storageMock = EasyMock.createMock(Storage.class);
-    EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(BLOB_PAGE);
-    for (BlobInfo info : BLOB_LIST) {
+    EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(blobPage);
+    for (BlobInfo info : blobList) {
       EasyMock.expect(storageMock.delete(BUCKET_NAME, info.name())).andReturn(true);
     }
     EasyMock.expect(storageMock.delete(BUCKET_NAME)).andThrow(FATAL_EXCEPTION);
diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/SerializationTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/SerializationTest.java
index 8bef27cb0cd0..c9b957bb936a 100644
--- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/SerializationTest.java
+++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/SerializationTest.java
@@ -42,6 +42,7 @@
 
 public class SerializationTest {
 
+  private static final Storage STORAGE = StorageOptions.builder().projectId("p").build().service();
   private static final Acl.Domain ACL_DOMAIN = new Acl.Domain("domain");
   private static final Acl.Group ACL_GROUP = new Acl.Group("group");
   private static final Acl.Project ACL_PROJECT_ = new Acl.Project(ProjectRole.VIEWERS, "pid");
@@ -50,16 +51,18 @@ public class SerializationTest {
   private static final Acl ACL = Acl.of(ACL_DOMAIN, Acl.Role.OWNER);
   private static final BlobInfo BLOB_INFO = BlobInfo.builder("b", "n").build();
   private static final BucketInfo BUCKET_INFO = BucketInfo.of("b");
+  private static final Blob BLOB = new Blob(STORAGE, new BlobInfo.BuilderImpl(BLOB_INFO));
+  private static final Bucket BUCKET = new Bucket(STORAGE, new BucketInfo.BuilderImpl(BUCKET_INFO));
   private static final Cors.Origin ORIGIN = Cors.Origin.any();
   private static final Cors CORS =
       Cors.builder().maxAgeSeconds(1).origins(Collections.singleton(ORIGIN)).build();
   private static final BatchRequest BATCH_REQUEST = BatchRequest.builder().delete("B", "N").build();
   private static final BatchResponse BATCH_RESPONSE = new BatchResponse(
       Collections.singletonList(BatchResponse.Result.of(true)),
-      Collections.>emptyList(),
-      Collections.>emptyList());
-  private static final PageImpl PAGE_RESULT = new PageImpl<>(
-      null, "c", Collections.singletonList(BlobInfo.builder("b", "n").build()));
+      Collections.>emptyList(),
+      Collections.>emptyList());
+  private static final PageImpl PAGE_RESULT =
+      new PageImpl<>(null, "c", Collections.singletonList(BLOB));
   private static final Storage.BlobListOption BLOB_LIST_OPTIONS =
       Storage.BlobListOption.maxResults(100);
   private static final Storage.BlobSourceOption BLOB_SOURCE_OPTIONS =
@@ -96,9 +99,9 @@ public void testServiceOptions() throws Exception {
   @Test
   public void testModelAndRequests() throws Exception {
     Serializable[] objects = {ACL_DOMAIN, ACL_GROUP, ACL_PROJECT_, ACL_USER, ACL_RAW, ACL,
-        BLOB_INFO, BUCKET_INFO, ORIGIN, CORS, BATCH_REQUEST, BATCH_RESPONSE, PAGE_RESULT,
-        BLOB_LIST_OPTIONS, BLOB_SOURCE_OPTIONS, BLOB_TARGET_OPTIONS, BUCKET_LIST_OPTIONS,
-        BUCKET_SOURCE_OPTIONS, BUCKET_TARGET_OPTIONS};
+        BLOB_INFO, BLOB, BUCKET_INFO, BUCKET, ORIGIN, CORS, BATCH_REQUEST, BATCH_RESPONSE,
+        PAGE_RESULT, BLOB_LIST_OPTIONS, BLOB_SOURCE_OPTIONS, BLOB_TARGET_OPTIONS,
+        BUCKET_LIST_OPTIONS, BUCKET_SOURCE_OPTIONS, BUCKET_TARGET_OPTIONS};
     for (Serializable obj : objects) {
       Object copy = serializeAndDeserialize(obj);
       assertEquals(obj, obj);
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 f32a51507857..f49938c5e9c1 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
@@ -238,6 +238,9 @@ public long millis() {
   private StorageRpc storageRpcMock;
   private Storage storage;
 
+  private Blob expectedBlob1, expectedBlob2, expectedBlob3;
+  private Bucket expectedBucket1, expectedBucket2;
+
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
@@ -272,10 +275,23 @@ public void tearDown() throws Exception {
     EasyMock.verify(rpcFactoryMock, storageRpcMock);
   }
 
+  private void initializeService() {
+    storage = options.service();
+    initializeServiceDependentObjects();
+  }
+
+  private void initializeServiceDependentObjects() {
+    expectedBlob1 = new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO1));
+    expectedBlob2 = new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO2));
+    expectedBlob3 = new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO3));
+    expectedBucket1 = new Bucket(storage, new BucketInfo.BuilderImpl(BUCKET_INFO1));
+    expectedBucket2 = new Bucket(storage, new BucketInfo.BuilderImpl(BUCKET_INFO2));
+  }
+
   @Test
   public void testGetOptions() {
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     assertSame(options, storage.options());
   }
 
@@ -284,9 +300,9 @@ public void testCreateBucket() {
     EasyMock.expect(storageRpcMock.create(BUCKET_INFO1.toPb(), EMPTY_RPC_OPTIONS))
         .andReturn(BUCKET_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BucketInfo bucket = storage.create(BUCKET_INFO1);
-    assertEquals(BUCKET_INFO1.toPb(), bucket.toPb());
+    initializeService();
+    Bucket bucket = storage.create(BUCKET_INFO1);
+    assertEquals(expectedBucket1, bucket);
   }
 
   @Test
@@ -294,10 +310,10 @@ public void testCreateBucketWithOptions() {
     EasyMock.expect(storageRpcMock.create(BUCKET_INFO1.toPb(), BUCKET_TARGET_OPTIONS))
         .andReturn(BUCKET_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BucketInfo bucket =
+    initializeService();
+    Bucket bucket =
         storage.create(BUCKET_INFO1, BUCKET_TARGET_METAGENERATION, BUCKET_TARGET_PREDEFINED_ACL);
-    assertEquals(BUCKET_INFO1, bucket);
+    assertEquals(expectedBucket1, bucket);
   }
 
   @Test
@@ -309,9 +325,9 @@ public void testCreateBlob() throws IOException {
         EasyMock.eq(EMPTY_RPC_OPTIONS)))
         .andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob = storage.create(BLOB_INFO1, BLOB_CONTENT);
-    assertEquals(BLOB_INFO1, blob);
+    initializeService();
+    Blob blob = storage.create(BLOB_INFO1, BLOB_CONTENT);
+    assertEquals(expectedBlob1, blob);
     ByteArrayInputStream byteStream = capturedStream.getValue();
     byte[] streamBytes = new byte[BLOB_CONTENT.length];
     assertEquals(BLOB_CONTENT.length, byteStream.read(streamBytes));
@@ -332,9 +348,9 @@ public void testCreateEmptyBlob() throws IOException {
         EasyMock.eq(EMPTY_RPC_OPTIONS)))
         .andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob = storage.create(BLOB_INFO1);
-    assertEquals(BLOB_INFO1, blob);
+    initializeService();
+    Blob blob = storage.create(BLOB_INFO1);
+    assertEquals(expectedBlob1, blob);
     ByteArrayInputStream byteStream = capturedStream.getValue();
     byte[] streamBytes = new byte[BLOB_CONTENT.length];
     assertEquals(-1, byteStream.read(streamBytes));
@@ -353,11 +369,11 @@ public void testCreateBlobWithOptions() throws IOException {
         EasyMock.eq(BLOB_TARGET_OPTIONS_CREATE)))
         .andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob =
+    initializeService();
+    Blob blob =
         storage.create(BLOB_INFO1, BLOB_CONTENT, BLOB_TARGET_METAGENERATION, BLOB_TARGET_NOT_EXIST,
             BLOB_TARGET_PREDEFINED_ACL);
-    assertEquals(BLOB_INFO1, blob);
+    assertEquals(expectedBlob1, blob);
     ByteArrayInputStream byteStream = capturedStream.getValue();
     byte[] streamBytes = new byte[BLOB_CONTENT.length];
     assertEquals(BLOB_CONTENT.length, byteStream.read(streamBytes));
@@ -374,9 +390,9 @@ public void testCreateBlobFromStream() {
     EasyMock.expect(storageRpcMock.create(infoWithoutHashes.toPb(), fileStream, EMPTY_RPC_OPTIONS))
         .andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob = storage.create(infoWithHashes, fileStream);
-    assertEquals(BLOB_INFO1, blob);
+    initializeService();
+    Blob blob = storage.create(infoWithHashes, fileStream);
+    assertEquals(expectedBlob1, blob);
   }
 
   @Test
@@ -384,9 +400,9 @@ public void testGetBucket() {
     EasyMock.expect(storageRpcMock.get(BucketInfo.of(BUCKET_NAME1).toPb(), EMPTY_RPC_OPTIONS))
         .andReturn(BUCKET_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BucketInfo bucket = storage.get(BUCKET_NAME1);
-    assertEquals(BUCKET_INFO1, bucket);
+    initializeService();
+    Bucket bucket = storage.get(BUCKET_NAME1);
+    assertEquals(expectedBucket1, bucket);
   }
 
   @Test
@@ -394,9 +410,9 @@ public void testGetBucketWithOptions() {
     EasyMock.expect(storageRpcMock.get(BucketInfo.of(BUCKET_NAME1).toPb(), BUCKET_GET_OPTIONS))
         .andReturn(BUCKET_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BucketInfo bucket = storage.get(BUCKET_NAME1, BUCKET_GET_METAGENERATION);
-    assertEquals(BUCKET_INFO1, bucket);
+    initializeService();
+    Bucket bucket = storage.get(BUCKET_NAME1, BUCKET_GET_METAGENERATION);
+    assertEquals(expectedBucket1, bucket);
   }
 
   @Test
@@ -405,8 +421,8 @@ public void testGetBucketWithSelectedFields() {
     EasyMock.expect(storageRpcMock.get(EasyMock.eq(BucketInfo.of(BUCKET_NAME1).toPb()),
         EasyMock.capture(capturedOptions))).andReturn(BUCKET_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BucketInfo bucket = storage.get(BUCKET_NAME1, BUCKET_GET_METAGENERATION, BUCKET_GET_FIELDS);
+    initializeService();
+    Bucket bucket = storage.get(BUCKET_NAME1, BUCKET_GET_METAGENERATION, BUCKET_GET_FIELDS);
     assertEquals(BUCKET_GET_METAGENERATION.value(),
         capturedOptions.getValue().get(BUCKET_GET_METAGENERATION.rpcOption()));
     String selector = (String) capturedOptions.getValue().get(BLOB_GET_FIELDS.rpcOption());
@@ -423,8 +439,8 @@ public void testGetBucketWithEmptyFields() {
     EasyMock.expect(storageRpcMock.get(EasyMock.eq(BucketInfo.of(BUCKET_NAME1).toPb()),
         EasyMock.capture(capturedOptions))).andReturn(BUCKET_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BucketInfo bucket = storage.get(BUCKET_NAME1, BUCKET_GET_METAGENERATION,
+    initializeService();
+    Bucket bucket = storage.get(BUCKET_NAME1, BUCKET_GET_METAGENERATION,
         BUCKET_GET_EMPTY_FIELDS);
     assertEquals(BUCKET_GET_METAGENERATION.value(),
         capturedOptions.getValue().get(BUCKET_GET_METAGENERATION.rpcOption()));
@@ -440,9 +456,9 @@ public void testGetBlob() {
         storageRpcMock.get(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), EMPTY_RPC_OPTIONS))
         .andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob = storage.get(BUCKET_NAME1, BLOB_NAME1);
-    assertEquals(BLOB_INFO1, blob);
+    initializeService();
+    Blob blob = storage.get(BUCKET_NAME1, BLOB_NAME1);
+    assertEquals(expectedBlob1, blob);
   }
 
   @Test
@@ -451,10 +467,10 @@ public void testGetBlobWithOptions() {
         storageRpcMock.get(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), BLOB_GET_OPTIONS))
         .andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob =
+    initializeService();
+    Blob blob =
         storage.get(BUCKET_NAME1, BLOB_NAME1, BLOB_GET_METAGENERATION, BLOB_GET_GENERATION);
-    assertEquals(BLOB_INFO1, blob);
+    assertEquals(expectedBlob1, blob);
   }
 
   @Test
@@ -463,10 +479,10 @@ public void testGetBlobWithOptionsFromBlobId() {
         storageRpcMock.get(BLOB_INFO1.blobId().toPb(), BLOB_GET_OPTIONS))
         .andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob =
+    initializeService();
+    Blob blob =
         storage.get(BLOB_INFO1.blobId(), BLOB_GET_METAGENERATION, BLOB_GET_GENERATION_FROM_BLOB_ID);
-    assertEquals(BLOB_INFO1, blob);
+    assertEquals(expectedBlob1, blob);
   }
 
   @Test
@@ -475,8 +491,9 @@ public void testGetBlobWithSelectedFields() {
     EasyMock.expect(storageRpcMock.get(EasyMock.eq(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb()),
         EasyMock.capture(capturedOptions))).andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob = storage.get(BUCKET_NAME1, BLOB_NAME1, BLOB_GET_METAGENERATION,
+    initializeService();
+    Blob blob = storage.get(
+        BUCKET_NAME1, BLOB_NAME1, BLOB_GET_METAGENERATION,
         BLOB_GET_GENERATION, BLOB_GET_FIELDS);
     assertEquals(BLOB_GET_METAGENERATION.value(),
         capturedOptions.getValue().get(BLOB_GET_METAGENERATION.rpcOption()));
@@ -488,7 +505,7 @@ public void testGetBlobWithSelectedFields() {
     assertTrue(selector.contains("contentType"));
     assertTrue(selector.contains("crc32c"));
     assertEquals(30, selector.length());
-    assertEquals(BLOB_INFO1, blob);
+    assertEquals(expectedBlob1, blob);
   }
 
   @Test
@@ -497,8 +514,8 @@ public void testGetBlobWithEmptyFields() {
     EasyMock.expect(storageRpcMock.get(EasyMock.eq(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb()),
         EasyMock.capture(capturedOptions))).andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob = storage.get(BUCKET_NAME1, BLOB_NAME1, BLOB_GET_METAGENERATION,
+    initializeService();
+    Blob blob = storage.get(BUCKET_NAME1, BLOB_NAME1, BLOB_GET_METAGENERATION,
         BLOB_GET_GENERATION, BLOB_GET_EMPTY_FIELDS);
     assertEquals(BLOB_GET_METAGENERATION.value(),
         capturedOptions.getValue().get(BLOB_GET_METAGENERATION.rpcOption()));
@@ -508,21 +525,22 @@ public void testGetBlobWithEmptyFields() {
     assertTrue(selector.contains("bucket"));
     assertTrue(selector.contains("name"));
     assertEquals(11, selector.length());
-    assertEquals(BLOB_INFO1, blob);
+    assertEquals(expectedBlob1, blob);
   }
 
   @Test
   public void testListBuckets() {
     String cursor = "cursor";
-    ImmutableList bucketList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
+    ImmutableList bucketInfoList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
     Tuple> result =
-        Tuple.of(cursor, Iterables.transform(bucketList, BucketInfo.TO_PB_FUNCTION));
+        Tuple.of(cursor, Iterables.transform(bucketInfoList, BucketInfo.TO_PB_FUNCTION));
     EasyMock.expect(storageRpcMock.list(EMPTY_RPC_OPTIONS)).andReturn(result);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    Page page = storage.list();
+    initializeService();
+    ImmutableList bucketList = ImmutableList.of(expectedBucket1, expectedBucket2);
+    Page page = storage.list();
     assertEquals(cursor, page.nextPageCursor());
-    assertArrayEquals(bucketList.toArray(), Iterables.toArray(page.values(), BucketInfo.class));
+    assertArrayEquals(bucketList.toArray(), Iterables.toArray(page.values(), Bucket.class));
   }
 
   @Test
@@ -530,38 +548,39 @@ public void testListBucketsEmpty() {
     EasyMock.expect(storageRpcMock.list(EMPTY_RPC_OPTIONS)).andReturn(
         Tuple.>of(null, null));
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    Page page = storage.list();
+    initializeService();
+    Page page = storage.list();
     assertNull(page.nextPageCursor());
-    assertArrayEquals(ImmutableList.of().toArray(),
-        Iterables.toArray(page.values(), BucketInfo.class));
+    assertArrayEquals(ImmutableList.of().toArray(), Iterables.toArray(page.values(), Bucket.class));
   }
 
   @Test
   public void testListBucketsWithOptions() {
     String cursor = "cursor";
-    ImmutableList bucketList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
+    ImmutableList bucketInfoList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
     Tuple> result =
-        Tuple.of(cursor, Iterables.transform(bucketList, BucketInfo.TO_PB_FUNCTION));
+        Tuple.of(cursor, Iterables.transform(bucketInfoList, BucketInfo.TO_PB_FUNCTION));
     EasyMock.expect(storageRpcMock.list(BUCKET_LIST_OPTIONS)).andReturn(result);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    Page page = storage.list(BUCKET_LIST_MAX_RESULT, BUCKET_LIST_PREFIX);
+    initializeService();
+    ImmutableList bucketList = ImmutableList.of(expectedBucket1, expectedBucket2);
+    Page page = storage.list(BUCKET_LIST_MAX_RESULT, BUCKET_LIST_PREFIX);
     assertEquals(cursor, page.nextPageCursor());
-    assertArrayEquals(bucketList.toArray(), Iterables.toArray(page.values(), BucketInfo.class));
+    assertArrayEquals(bucketList.toArray(), Iterables.toArray(page.values(), Bucket.class));
   }
 
   @Test
   public void testListBucketsWithSelectedFields() {
     String cursor = "cursor";
     Capture> capturedOptions = Capture.newInstance();
-    ImmutableList bucketList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
+    ImmutableList bucketInfoList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
     Tuple> result =
-        Tuple.of(cursor, Iterables.transform(bucketList, BucketInfo.TO_PB_FUNCTION));
+        Tuple.of(cursor, Iterables.transform(bucketInfoList, BucketInfo.TO_PB_FUNCTION));
     EasyMock.expect(storageRpcMock.list(EasyMock.capture(capturedOptions))).andReturn(result);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    Page page = storage.list(BUCKET_LIST_FIELDS);
+    initializeService();
+    ImmutableList bucketList = ImmutableList.of(expectedBucket1, expectedBucket2);
+    Page page = storage.list(BUCKET_LIST_FIELDS);
     String selector = (String) capturedOptions.getValue().get(BLOB_LIST_FIELDS.rpcOption());
     assertTrue(selector.contains("items"));
     assertTrue(selector.contains("name"));
@@ -569,40 +588,42 @@ public void testListBucketsWithSelectedFields() {
     assertTrue(selector.contains("location"));
     assertEquals(24, selector.length());
     assertEquals(cursor, page.nextPageCursor());
-    assertArrayEquals(bucketList.toArray(), Iterables.toArray(page.values(), BucketInfo.class));
+    assertArrayEquals(bucketList.toArray(), Iterables.toArray(page.values(), Bucket.class));
   }
 
   @Test
   public void testListBucketsWithEmptyFields() {
     String cursor = "cursor";
     Capture> capturedOptions = Capture.newInstance();
-    ImmutableList bucketList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
+    ImmutableList bucketInfoList = ImmutableList.of(BUCKET_INFO1, BUCKET_INFO2);
     Tuple> result =
-        Tuple.of(cursor, Iterables.transform(bucketList, BucketInfo.TO_PB_FUNCTION));
+        Tuple.of(cursor, Iterables.transform(bucketInfoList, BucketInfo.TO_PB_FUNCTION));
     EasyMock.expect(storageRpcMock.list(EasyMock.capture(capturedOptions))).andReturn(result);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    Page page = storage.list(BUCKET_LIST_EMPTY_FIELDS);
+    initializeService();
+    ImmutableList bucketList = ImmutableList.of(expectedBucket1, expectedBucket2);
+    Page page = storage.list(BUCKET_LIST_EMPTY_FIELDS);
     String selector = (String) capturedOptions.getValue().get(BLOB_LIST_FIELDS.rpcOption());
     assertTrue(selector.contains("items"));
     assertTrue(selector.contains("name"));
     assertEquals(11, selector.length());
     assertEquals(cursor, page.nextPageCursor());
-    assertArrayEquals(bucketList.toArray(), Iterables.toArray(page.values(), BucketInfo.class));
+    assertArrayEquals(bucketList.toArray(), Iterables.toArray(page.values(), Bucket.class));
   }
 
   @Test
   public void testListBlobs() {
     String cursor = "cursor";
-    ImmutableList blobList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
+    ImmutableList blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple> result =
-        Tuple.of(cursor, Iterables.transform(blobList, BlobInfo.TO_PB_FUNCTION));
+        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
     EasyMock.expect(storageRpcMock.list(BUCKET_NAME1, EMPTY_RPC_OPTIONS)).andReturn(result);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    Page page = storage.list(BUCKET_NAME1);
+    initializeService();
+    ImmutableList blobList = ImmutableList.of(expectedBlob1, expectedBlob2);
+    Page page = storage.list(BUCKET_NAME1);
     assertEquals(cursor, page.nextPageCursor());
-    assertArrayEquals(blobList.toArray(), Iterables.toArray(page.values(), BlobInfo.class));
+    assertArrayEquals(blobList.toArray(), Iterables.toArray(page.values(), Blob.class));
   }
 
   @Test
@@ -611,40 +632,41 @@ public void testListBlobsEmpty() {
         .andReturn(Tuple.>of(
             null, null));
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    Page page = storage.list(BUCKET_NAME1);
+    initializeService();
+    Page page = storage.list(BUCKET_NAME1);
     assertNull(page.nextPageCursor());
-    assertArrayEquals(ImmutableList.of().toArray(),
-        Iterables.toArray(page.values(), BlobInfo.class));
+    assertArrayEquals(ImmutableList.of().toArray(), Iterables.toArray(page.values(), Blob.class));
   }
 
   @Test
   public void testListBlobsWithOptions() {
     String cursor = "cursor";
-    ImmutableList blobList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
+    ImmutableList blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple> result =
-        Tuple.of(cursor, Iterables.transform(blobList, BlobInfo.TO_PB_FUNCTION));
+        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
     EasyMock.expect(storageRpcMock.list(BUCKET_NAME1, BLOB_LIST_OPTIONS)).andReturn(result);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    Page page = storage.list(BUCKET_NAME1, BLOB_LIST_MAX_RESULT, BLOB_LIST_PREFIX);
+    initializeService();
+    ImmutableList blobList = ImmutableList.of(expectedBlob1, expectedBlob2);
+    Page page = storage.list(BUCKET_NAME1, BLOB_LIST_MAX_RESULT, BLOB_LIST_PREFIX);
     assertEquals(cursor, page.nextPageCursor());
-    assertArrayEquals(blobList.toArray(), Iterables.toArray(page.values(), BlobInfo.class));
+    assertArrayEquals(blobList.toArray(), Iterables.toArray(page.values(), Blob.class));
   }
 
   @Test
   public void testListBlobsWithSelectedFields() {
     String cursor = "cursor";
     Capture> capturedOptions = Capture.newInstance();
-    ImmutableList blobList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
+    ImmutableList blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple> result =
-        Tuple.of(cursor, Iterables.transform(blobList, BlobInfo.TO_PB_FUNCTION));
+        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
     EasyMock.expect(
         storageRpcMock.list(EasyMock.eq(BUCKET_NAME1), EasyMock.capture(capturedOptions)))
         .andReturn(result);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    Page page =
+    initializeService();
+    ImmutableList blobList = ImmutableList.of(expectedBlob1, expectedBlob2);
+    Page page =
         storage.list(BUCKET_NAME1, BLOB_LIST_MAX_RESULT, BLOB_LIST_PREFIX, BLOB_LIST_FIELDS);
     assertEquals(BLOB_LIST_MAX_RESULT.value(),
         capturedOptions.getValue().get(BLOB_LIST_MAX_RESULT.rpcOption()));
@@ -658,22 +680,23 @@ public void testListBlobsWithSelectedFields() {
     assertTrue(selector.contains("md5Hash"));
     assertEquals(38, selector.length());
     assertEquals(cursor, page.nextPageCursor());
-    assertArrayEquals(blobList.toArray(), Iterables.toArray(page.values(), BlobInfo.class));
+    assertArrayEquals(blobList.toArray(), Iterables.toArray(page.values(), Blob.class));
   }
 
   @Test
   public void testListBlobsWithEmptyFields() {
     String cursor = "cursor";
     Capture> capturedOptions = Capture.newInstance();
-    ImmutableList blobList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
+    ImmutableList blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
     Tuple> result =
-        Tuple.of(cursor, Iterables.transform(blobList, BlobInfo.TO_PB_FUNCTION));
+        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
     EasyMock.expect(
         storageRpcMock.list(EasyMock.eq(BUCKET_NAME1), EasyMock.capture(capturedOptions)))
         .andReturn(result);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    Page page =
+    initializeService();
+    ImmutableList blobList = ImmutableList.of(expectedBlob1, expectedBlob2);
+    Page page =
         storage.list(BUCKET_NAME1, BLOB_LIST_MAX_RESULT, BLOB_LIST_PREFIX, BLOB_LIST_EMPTY_FIELDS);
     assertEquals(BLOB_LIST_MAX_RESULT.value(),
         capturedOptions.getValue().get(BLOB_LIST_MAX_RESULT.rpcOption()));
@@ -685,7 +708,7 @@ public void testListBlobsWithEmptyFields() {
     assertTrue(selector.contains("name"));
     assertEquals(18, selector.length());
     assertEquals(cursor, page.nextPageCursor());
-    assertArrayEquals(blobList.toArray(), Iterables.toArray(page.values(), BlobInfo.class));
+    assertArrayEquals(blobList.toArray(), Iterables.toArray(page.values(), Blob.class));
   }
 
   @Test
@@ -694,9 +717,9 @@ public void testUpdateBucket() {
     EasyMock.expect(storageRpcMock.patch(updatedBucketInfo.toPb(), EMPTY_RPC_OPTIONS))
         .andReturn(updatedBucketInfo.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BucketInfo bucket = storage.update(updatedBucketInfo);
-    assertEquals(updatedBucketInfo, bucket);
+    initializeService();
+    Bucket bucket = storage.update(updatedBucketInfo);
+    assertEquals(new Bucket(storage, new BucketInfo.BuilderImpl(updatedBucketInfo)), bucket);
   }
 
   @Test
@@ -705,11 +728,11 @@ public void testUpdateBucketWithOptions() {
     EasyMock.expect(storageRpcMock.patch(updatedBucketInfo.toPb(), BUCKET_TARGET_OPTIONS))
         .andReturn(updatedBucketInfo.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BucketInfo bucket =
+    initializeService();
+    Bucket bucket =
         storage.update(updatedBucketInfo, BUCKET_TARGET_METAGENERATION,
             BUCKET_TARGET_PREDEFINED_ACL);
-    assertEquals(updatedBucketInfo, bucket);
+    assertEquals(new Bucket(storage, new BucketInfo.BuilderImpl(updatedBucketInfo)), bucket);
   }
 
   @Test
@@ -718,9 +741,9 @@ public void testUpdateBlob() {
     EasyMock.expect(storageRpcMock.patch(updatedBlobInfo.toPb(), EMPTY_RPC_OPTIONS))
         .andReturn(updatedBlobInfo.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob = storage.update(updatedBlobInfo);
-    assertEquals(updatedBlobInfo, blob);
+    initializeService();
+    Blob blob = storage.update(updatedBlobInfo);
+    assertEquals(new Blob(storage, new BlobInfo.BuilderImpl(updatedBlobInfo)), blob);
   }
 
   @Test
@@ -729,10 +752,10 @@ public void testUpdateBlobWithOptions() {
     EasyMock.expect(storageRpcMock.patch(updatedBlobInfo.toPb(), BLOB_TARGET_OPTIONS_UPDATE))
         .andReturn(updatedBlobInfo.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob =
+    initializeService();
+    Blob blob =
         storage.update(updatedBlobInfo, BLOB_TARGET_METAGENERATION, BLOB_TARGET_PREDEFINED_ACL);
-    assertEquals(updatedBlobInfo, blob);
+    assertEquals(new Blob(storage, new BlobInfo.BuilderImpl(updatedBlobInfo)), blob);
   }
 
   @Test
@@ -740,7 +763,7 @@ public void testDeleteBucket() {
     EasyMock.expect(storageRpcMock.delete(BucketInfo.of(BUCKET_NAME1).toPb(), EMPTY_RPC_OPTIONS))
         .andReturn(true);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     assertTrue(storage.delete(BUCKET_NAME1));
   }
 
@@ -750,7 +773,7 @@ public void testDeleteBucketWithOptions() {
         .expect(storageRpcMock.delete(BucketInfo.of(BUCKET_NAME1).toPb(), BUCKET_SOURCE_OPTIONS))
         .andReturn(true);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     assertTrue(storage.delete(BUCKET_NAME1, BUCKET_SOURCE_METAGENERATION));
   }
 
@@ -760,7 +783,7 @@ public void testDeleteBlob() {
         storageRpcMock.delete(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), EMPTY_RPC_OPTIONS))
         .andReturn(true);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     assertTrue(storage.delete(BUCKET_NAME1, BLOB_NAME1));
   }
 
@@ -770,7 +793,7 @@ public void testDeleteBlobWithOptions() {
         storageRpcMock.delete(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), BLOB_SOURCE_OPTIONS))
         .andReturn(true);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     assertTrue(storage.delete(BUCKET_NAME1, BLOB_NAME1, BLOB_SOURCE_GENERATION,
         BLOB_SOURCE_METAGENERATION));
   }
@@ -781,7 +804,7 @@ public void testDeleteBlobWithOptionsFromBlobId() {
         storageRpcMock.delete(BLOB_INFO1.blobId().toPb(), BLOB_SOURCE_OPTIONS))
         .andReturn(true);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     assertTrue(storage.delete(BLOB_INFO1.blobId(), BLOB_SOURCE_GENERATION_FROM_BLOB_ID,
         BLOB_SOURCE_METAGENERATION));
   }
@@ -795,9 +818,9 @@ public void testCompose() {
     EasyMock.expect(storageRpcMock.compose(ImmutableList.of(BLOB_INFO2.toPb(), BLOB_INFO3.toPb()),
         BLOB_INFO1.toPb(), EMPTY_RPC_OPTIONS)).andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob = storage.compose(req);
-    assertEquals(BLOB_INFO1, blob);
+    initializeService();
+    Blob blob = storage.compose(req);
+    assertEquals(expectedBlob1, blob);
   }
 
   @Test
@@ -810,9 +833,9 @@ public void testComposeWithOptions() {
     EasyMock.expect(storageRpcMock.compose(ImmutableList.of(BLOB_INFO2.toPb(), BLOB_INFO3.toPb()),
         BLOB_INFO1.toPb(), BLOB_TARGET_OPTIONS_COMPOSE)).andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    BlobInfo blob = storage.compose(req);
-    assertEquals(BLOB_INFO1, blob);
+    initializeService();
+    Blob blob = storage.compose(req);
+    assertEquals(expectedBlob1, blob);
   }
 
   @Test
@@ -824,7 +847,7 @@ public void testCopy() {
         false, "token", 21L);
     EasyMock.expect(storageRpcMock.openRewrite(rpcRequest)).andReturn(rpcResponse);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     CopyWriter writer = storage.copy(request);
     assertEquals(42L, writer.blobSize());
     assertEquals(21L, writer.totalBytesCopied());
@@ -844,7 +867,7 @@ public void testCopyWithOptions() {
         false, "token", 21L);
     EasyMock.expect(storageRpcMock.openRewrite(rpcRequest)).andReturn(rpcResponse);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     CopyWriter writer = storage.copy(request);
     assertEquals(42L, writer.blobSize());
     assertEquals(21L, writer.totalBytesCopied());
@@ -864,7 +887,7 @@ public void testCopyWithOptionsFromBlobId() {
         new StorageRpc.RewriteResponse(rpcRequest, null, 42L, false, "token", 21L);
     EasyMock.expect(storageRpcMock.openRewrite(rpcRequest)).andReturn(rpcResponse);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     CopyWriter writer = storage.copy(request);
     assertEquals(42L, writer.blobSize());
     assertEquals(21L, writer.totalBytesCopied());
@@ -883,7 +906,7 @@ public void testCopyMultipleRequests() {
     EasyMock.expect(storageRpcMock.openRewrite(rpcRequest)).andReturn(rpcResponse1);
     EasyMock.expect(storageRpcMock.continueRewrite(rpcResponse1)).andReturn(rpcResponse2);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     CopyWriter writer = storage.copy(request);
     assertEquals(42L, writer.blobSize());
     assertEquals(21L, writer.totalBytesCopied());
@@ -900,7 +923,7 @@ public void testReadAllBytes() {
         storageRpcMock.load(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), EMPTY_RPC_OPTIONS))
         .andReturn(BLOB_CONTENT);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     byte[] readBytes = storage.readAllBytes(BUCKET_NAME1, BLOB_NAME1);
     assertArrayEquals(BLOB_CONTENT, readBytes);
   }
@@ -911,7 +934,7 @@ public void testReadAllBytesWithOptions() {
         storageRpcMock.load(BlobId.of(BUCKET_NAME1, BLOB_NAME1).toPb(), BLOB_SOURCE_OPTIONS))
         .andReturn(BLOB_CONTENT);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     byte[] readBytes = storage.readAllBytes(BUCKET_NAME1, BLOB_NAME1, BLOB_SOURCE_GENERATION,
         BLOB_SOURCE_METAGENERATION);
     assertArrayEquals(BLOB_CONTENT, readBytes);
@@ -923,7 +946,7 @@ public void testReadAllBytesWithOptionsFromBlobId() {
         storageRpcMock.load(BLOB_INFO1.blobId().toPb(), BLOB_SOURCE_OPTIONS))
         .andReturn(BLOB_CONTENT);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     byte[] readBytes = storage.readAllBytes(BLOB_INFO1.blobId(),
         BLOB_SOURCE_GENERATION_FROM_BLOB_ID, BLOB_SOURCE_METAGENERATION);
     assertArrayEquals(BLOB_CONTENT, readBytes);
@@ -970,11 +993,10 @@ public Tuple apply(StorageObject f) {
     StorageRpc.BatchResponse res =
         new StorageRpc.BatchResponse(deleteResult, updateResult, getResult);
 
-
     Capture capturedBatchRequest = Capture.newInstance();
     EasyMock.expect(storageRpcMock.batch(EasyMock.capture(capturedBatchRequest))).andReturn(res);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     BatchResponse batchResponse = storage.submit(req);
 
     // Verify captured StorageRpc.BatchRequest
@@ -1012,7 +1034,7 @@ public Tuple apply(StorageObject f) {
   @Test
   public void testReader() {
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     ReadChannel channel = storage.reader(BUCKET_NAME1, BLOB_NAME1);
     assertNotNull(channel);
     assertTrue(channel.isOpen());
@@ -1025,7 +1047,7 @@ public void testReaderWithOptions() throws IOException {
         storageRpcMock.read(BLOB_INFO2.toPb(), BLOB_SOURCE_OPTIONS, 0, DEFAULT_CHUNK_SIZE))
         .andReturn(StorageRpc.Tuple.of("etag", result));
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     ReadChannel channel = storage.reader(BUCKET_NAME1, BLOB_NAME2, BLOB_SOURCE_GENERATION,
         BLOB_SOURCE_METAGENERATION);
     assertNotNull(channel);
@@ -1040,7 +1062,7 @@ public void testReaderWithOptionsFromBlobId() throws IOException {
         storageRpcMock.read(BLOB_INFO1.blobId().toPb(), BLOB_SOURCE_OPTIONS, 0, DEFAULT_CHUNK_SIZE))
         .andReturn(StorageRpc.Tuple.of("etag", result));
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     ReadChannel channel = storage.reader(BLOB_INFO1.blobId(),
         BLOB_SOURCE_GENERATION_FROM_BLOB_ID, BLOB_SOURCE_METAGENERATION);
     assertNotNull(channel);
@@ -1056,7 +1078,7 @@ public void testWriter() {
     EasyMock.expect(storageRpcMock.open(infoWithoutHashes.toPb(), EMPTY_RPC_OPTIONS))
         .andReturn("upload-id");
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     WriteChannel channel = storage.writer(infoWithHashes);
     assertNotNull(channel);
     assertTrue(channel.isOpen());
@@ -1068,7 +1090,7 @@ public void testWriterWithOptions() {
     EasyMock.expect(storageRpcMock.open(info.toPb(), BLOB_TARGET_OPTIONS_CREATE))
         .andReturn("upload-id");
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     WriteChannel channel = storage.writer(info, BLOB_WRITE_METAGENERATION, BLOB_WRITE_NOT_EXIST,
         BLOB_WRITE_PREDEFINED_ACL, BLOB_WRITE_CRC2C, BLOB_WRITE_MD5_HASH);
     assertNotNull(channel);
@@ -1154,12 +1176,11 @@ public Tuple apply(StorageObject f) {
     StorageRpc.BatchResponse res =
         new StorageRpc.BatchResponse(deleteResult, updateResult, getResult);
 
-
     Capture capturedBatchRequest = Capture.newInstance();
     EasyMock.expect(storageRpcMock.batch(EasyMock.capture(capturedBatchRequest))).andReturn(res);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    List resultBlobs = storage.get(blobId1, blobId2);
+    initializeService();
+    List resultBlobs = storage.get(blobId1, blobId2);
 
     // Verify captured StorageRpc.BatchRequest
     List>> capturedToGet =
@@ -1197,12 +1218,11 @@ public Tuple apply(StorageObject f) {
     StorageRpc.BatchResponse res =
         new StorageRpc.BatchResponse(deleteResult, updateResult, getResult);
 
-
     Capture capturedBatchRequest = Capture.newInstance();
     EasyMock.expect(storageRpcMock.batch(EasyMock.capture(capturedBatchRequest))).andReturn(res);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
-    List resultBlobs = storage.update(blobInfo1, blobInfo2);
+    initializeService();
+    List resultBlobs = storage.update(blobInfo1, blobInfo2);
 
     // Verify captured StorageRpc.BatchRequest
     List>> capturedToUpdate =
@@ -1243,7 +1263,7 @@ public Tuple apply(StorageObject f) {
     Capture capturedBatchRequest = Capture.newInstance();
     EasyMock.expect(storageRpcMock.batch(EasyMock.capture(capturedBatchRequest))).andReturn(res);
     EasyMock.replay(storageRpcMock);
-    storage = options.service();
+    initializeService();
     List deleteResults = storage.delete(blobInfo1.blobId(), blobInfo2.blobId());
 
     // Verify captured StorageRpc.BatchRequest
@@ -1270,8 +1290,9 @@ public void testRetryableException() {
         .andReturn(BLOB_INFO1.toPb());
     EasyMock.replay(storageRpcMock);
     storage = options.toBuilder().retryParams(RetryParams.defaultInstance()).build().service();
-    BlobInfo readBlob = storage.get(blob);
-    assertEquals(BLOB_INFO1, readBlob);
+    initializeServiceDependentObjects();
+    Blob readBlob = storage.get(blob);
+    assertEquals(expectedBlob1, readBlob);
   }
 
   @Test
@@ -1282,6 +1303,7 @@ public void testNonRetryableException() {
         .andThrow(new StorageException(501, exceptionMessage));
     EasyMock.replay(storageRpcMock);
     storage = options.toBuilder().retryParams(RetryParams.defaultInstance()).build().service();
+    initializeServiceDependentObjects();
     thrown.expect(StorageException.class);
     thrown.expectMessage(exceptionMessage);
     storage.get(blob);