From a76e6c40e3a77dc991d404a80287d5240ce8a0d9 Mon Sep 17 00:00:00 2001 From: Ajay Kannan Date: Fri, 22 Jan 2016 11:03:01 -0800 Subject: [PATCH] Make Project a subclass of ProjectInfo --- README.md | 16 +- .../examples/ResourceManagerExample.java | 3 +- gcloud-java-resourcemanager/README.md | 34 ++-- .../gcloud/resourcemanager/Project.java | 160 +++++++++++++++--- .../gcloud/resourcemanager/ProjectInfo.java | 120 ++++++++----- .../resourcemanager/ResourceManager.java | 14 +- .../resourcemanager/ResourceManagerImpl.java | 31 ++-- .../gcloud/resourcemanager/package-info.java | 10 +- .../gcloud/resourcemanager/ProjectTest.java | 125 ++++++++++---- .../ResourceManagerImplTest.java | 38 +++-- .../resourcemanager/SerializationTest.java | 10 +- 11 files changed, 398 insertions(+), 163 deletions(-) diff --git a/README.md b/README.md index 520bf8b62c55..f4bad65a7bc5 100644 --- a/README.md +++ b/README.md @@ -209,20 +209,22 @@ Google Cloud Resource Manager (Alpha) Here is a code snippet showing a simple usage example. Note that you must supply Google SDK credentials for this service, not other forms of authentication listed in the [Authentication section](#authentication). ```java -import com.google.gcloud.resourcemanager.ProjectInfo; +import com.google.gcloud.resourcemanager.Project; import com.google.gcloud.resourcemanager.ResourceManager; import com.google.gcloud.resourcemanager.ResourceManagerOptions; import java.util.Iterator; ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service(); -ProjectInfo myProject = resourceManager.get("some-project-id"); // Use an existing project's ID -ProjectInfo newProjectInfo = resourceManager.replace(myProject.toBuilder() - .addLabel("launch-status", "in-development").build()); -System.out.println("Updated the labels of project " + newProjectInfo.projectId() - + " to be " + newProjectInfo.labels()); +Project myProject = resourceManager.get("some-project-id"); // Use an existing project's ID +Project newProject = myProject.toBuilder() + .addLabel("launch-status", "in-development") + .build() + .replace(); +System.out.println("Updated the labels of project " + newProject.projectId() + + " to be " + newProject.labels()); // List all the projects you have permission to view. -Iterator projectIterator = resourceManager.list().iterateAll(); +Iterator projectIterator = resourceManager.list().iterateAll(); System.out.println("Projects I can view:"); while (projectIterator.hasNext()) { System.out.println(projectIterator.next().projectId()); diff --git a/gcloud-java-examples/src/main/java/com/google/gcloud/examples/ResourceManagerExample.java b/gcloud-java-examples/src/main/java/com/google/gcloud/examples/ResourceManagerExample.java index c1ba4e06cf7d..46ff82bfaf12 100644 --- a/gcloud-java-examples/src/main/java/com/google/gcloud/examples/ResourceManagerExample.java +++ b/gcloud-java-examples/src/main/java/com/google/gcloud/examples/ResourceManagerExample.java @@ -17,6 +17,7 @@ package com.google.gcloud.examples; import com.google.common.base.Joiner; +import com.google.gcloud.resourcemanager.Project; import com.google.gcloud.resourcemanager.ProjectInfo; import com.google.gcloud.resourcemanager.ResourceManager; import com.google.gcloud.resourcemanager.ResourceManagerOptions; @@ -64,7 +65,7 @@ public void run(ResourceManager resourceManager, String... args) { labels.put(args[i], ""); } } - ProjectInfo project = + Project project = resourceManager.create(ProjectInfo.builder(projectId).labels(labels).build()); System.out.printf( "Successfully created project '%s': %s.%n", projectId, projectDetails(project)); diff --git a/gcloud-java-resourcemanager/README.md b/gcloud-java-resourcemanager/README.md index d0e58af85e4e..63f2a9c80efb 100644 --- a/gcloud-java-resourcemanager/README.md +++ b/gcloud-java-resourcemanager/README.md @@ -83,6 +83,7 @@ ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().servi All you need to create a project is a globally unique project ID. You can also optionally attach a non-unique name and labels to your project. Read more about naming guidelines for project IDs, names, and labels [here](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects). To create a project, add the following import at the top of your file: ```java +import com.google.gcloud.resourcemanager.Project; import com.google.gcloud.resourcemanager.ProjectInfo; ``` @@ -90,26 +91,28 @@ Then add the following code to create a project (be sure to change `myProjectId` ```java String myProjectId = "my-globally-unique-project-id"; // Change to a unique project ID. -ProjectInfo myProject = resourceManager.create(ProjectInfo.builder(myProjectId).build()); +Project myProject = resourceManager.create(ProjectInfo.builder(myProjectId).build()); ``` -Note that the return value from `create` is a `ProjectInfo` that includes additional read-only information, like creation time, project number, and lifecycle state. Read more about these fields on the [Projects page](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects). +Note that the return value from `create` is a `Project` that includes additional read-only information, like creation time, project number, and lifecycle state. Read more about these fields on the [Projects page](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects). `Project`, a subclass of `ProjectInfo`, adds a layer of service-related functionality over `ProjectInfo`. #### Getting a specific project You can load a project if you know it's project ID and have read permissions to the project. For example, to get the project we just created we can do the following: ```java -ProjectInfo projectFromServer = resourceManager.get(myProjectId); +Project projectFromServer = resourceManager.get(myProjectId); ``` #### Editing a project -To edit a project, create a new `ProjectInfo` object and pass it in to the `ResourceManager.replace` method. +To edit a project, create a new `ProjectInfo` object and pass it in to the `Project.replace` method. For example, to add a label for the newly created project to denote that it's launch status is "in development", add the following code: ```java -ProjectInfo newProjectInfo = resourceManager.replace(projectFromServer.toBuilder() - .addLabel("launch-status", "in-development").build()); +Project newProject = myProject.toBuilder() + .addLabel("launch-status", "in-development") + .build() + .replace(); ``` Note that the values of the project you pass in to `replace` overwrite the server's values for non-read-only fields, namely `projectName` and `labels`. For example, if you create a project with `projectName` "some-project-name" and subsequently call replace using a `ProjectInfo` object that didn't set the `projectName`, then the server will unset the project's name. The server ignores any attempted changes to the read-only fields `projectNumber`, `lifecycleState`, and `createTime`. The `projectId` cannot change. @@ -124,7 +127,7 @@ import java.util.Iterator; Then add the following code to print a list of projects you can view: ```java -Iterator projectIterator = resourceManager.list().iterateAll(); +Iterator projectIterator = resourceManager.list().iterateAll(); System.out.println("Projects I can view:"); while (projectIterator.hasNext()) { System.out.println(projectIterator.next().projectId()); @@ -136,6 +139,7 @@ while (projectIterator.hasNext()) { Here we put together all the code shown above into one program. This program assumes that you are running from your own desktop and used the Google Cloud SDK to authenticate yourself. ```java +import com.google.gcloud.resourcemanager.Project; import com.google.gcloud.resourcemanager.ProjectInfo; import com.google.gcloud.resourcemanager.ResourceManager; import com.google.gcloud.resourcemanager.ResourceManagerOptions; @@ -151,20 +155,22 @@ public class GcloudJavaResourceManagerExample { // Create a project. String myProjectId = "my-globally-unique-project-id"; // Change to a unique project ID. - ProjectInfo myProject = resourceManager.create(ProjectInfo.builder(myProjectId).build()); + Project myProject = resourceManager.create(ProjectInfo.builder(myProjectId).build()); // Get a project from the server. - ProjectInfo projectFromServer = resourceManager.get(myProjectId); + Project projectFromServer = resourceManager.get(myProjectId); System.out.println("Got project " + projectFromServer.projectId() + " from the server."); // Update a project - ProjectInfo newProjectInfo = resourceManager.replace(myProject.toBuilder() - .addLabel("launch-status", "in-development").build()); - System.out.println("Updated the labels of project " + newProjectInfo.projectId() - + " to be " + newProjectInfo.labels()); + Project newProject = myProject.toBuilder() + .addLabel("launch-status", "in-development") + .build() + .replace(); + System.out.println("Updated the labels of project " + newProject.projectId() + + " to be " + newProject.labels()); // List all the projects you have permission to view. - Iterator projectIterator = resourceManager.list().iterateAll(); + Iterator projectIterator = resourceManager.list().iterateAll(); System.out.println("Projects I can view:"); while (projectIterator.hasNext()) { System.out.println(projectIterator.next().projectId()); 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 e329e1aa3714..f12a7ea50676 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 @@ -18,25 +18,110 @@ import static com.google.common.base.Preconditions.checkNotNull; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.Map; +import java.util.Objects; + /** * A Google Cloud Resource Manager project object. * *

A Project is a high-level Google Cloud Platform entity. It is a container for ACLs, APIs, * AppEngine Apps, VMs, and other Google Cloud Platform resources. This class' member variables are - * immutable. Methods that change or update the underlying Project information return a new Project - * instance. + * immutable. Methods that change or update the underlying Project information return a new Project + * instance. {@code Project} adds a layer of service-related functionality over {@link ProjectInfo}. */ -public class Project { +public class Project extends ProjectInfo { - private final ResourceManager resourceManager; - private final ProjectInfo info; + private static final long serialVersionUID = 6767630161335155133L; - /** - * Constructs a Project object that contains the ProjectInfo given. - */ - public Project(ResourceManager resourceManager, ProjectInfo projectInfo) { + private final ResourceManagerOptions options; + private transient ResourceManager resourceManager; + + 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(); + } + + Builder(Project project) { + this.resourceManager = project.resourceManager; + this.infoBuilder = new ProjectInfo.BuilderImpl(project); + } + + @Override + public Builder name(String name) { + infoBuilder.name(name); + return this; + } + + @Override + public Builder projectId(String projectId) { + infoBuilder.projectId(projectId); + return this; + } + + @Override + public Builder addLabel(String key, String value) { + infoBuilder.addLabel(key, value); + return this; + } + + @Override + public Builder removeLabel(String key) { + infoBuilder.removeLabel(key); + return this; + } + + @Override + public Builder clearLabels() { + infoBuilder.clearLabels(); + return this; + } + + @Override + public Builder labels(Map labels) { + infoBuilder.labels(labels); + return this; + } + + @Override + Builder projectNumber(Long projectNumber) { + infoBuilder.projectNumber(projectNumber); + return this; + } + + @Override + Builder state(State state) { + infoBuilder.state(state); + return this; + } + + @Override + Builder createTimeMillis(Long createTimeMillis) { + infoBuilder.createTimeMillis(createTimeMillis); + return this; + } + + @Override + Builder parent(ResourceId parent) { + infoBuilder.parent(parent); + return this; + } + + @Override + public Project build() { + return new Project(resourceManager, infoBuilder); + } + } + + Project(ResourceManager resourceManager, ProjectInfo.BuilderImpl infoBuilder) { + super(infoBuilder); this.resourceManager = checkNotNull(resourceManager); - this.info = checkNotNull(projectInfo); + this.options = resourceManager.options(); } /** @@ -46,15 +131,7 @@ public Project(ResourceManager resourceManager, ProjectInfo projectInfo) { * @throws ResourceManagerException upon failure */ public static Project get(ResourceManager resourceManager, String projectId) { - ProjectInfo projectInfo = resourceManager.get(projectId); - return projectInfo != null ? new Project(resourceManager, projectInfo) : null; - } - - /** - * Returns the {@link ProjectInfo} object associated with this Project. - */ - public ProjectInfo info() { - return info; + return resourceManager.get(projectId); } /** @@ -72,7 +149,7 @@ public ResourceManager resourceManager() { * @throws ResourceManagerException upon failure */ public Project reload() { - return Project.get(resourceManager, info.projectId()); + return Project.get(resourceManager, projectId()); } /** @@ -98,7 +175,7 @@ public Project reload() { * @throws ResourceManagerException upon failure */ public void delete() { - resourceManager.delete(info.projectId()); + resourceManager.delete(projectId()); } /** @@ -115,21 +192,52 @@ public void delete() { * @throws ResourceManagerException upon failure (including when the project can't be restored) */ public void undelete() { - resourceManager.undelete(info.projectId()); + resourceManager.undelete(projectId()); } /** - * Replaces the attributes of the project. + * Replaces the attributes of the project with the attributes of this project. * *

The caller must have modify permissions for this project. * * @see Cloud * Resource Manager update - * @return the ProjectInfo representing the new project metadata + * @return the Project representing the new project metadata * @throws ResourceManagerException upon failure */ - public Project replace(ProjectInfo projectInfo) { - return new Project(resourceManager, resourceManager.replace(checkNotNull(projectInfo))); + 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); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Project && Objects.equals(toPb(), ((Project) obj).toPb()) + && Objects.equals(options, ((Project) obj).options); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), options); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + this.resourceManager = options.service(); + } + + static Project fromPb(ResourceManager resourceManager, + com.google.api.services.cloudresourcemanager.model.Project answer) { + ProjectInfo info = ProjectInfo.fromPb(answer); + return new Project(resourceManager, new ProjectInfo.BuilderImpl(info)); } } 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 2cb8a2d93ad2..7553a207cd29 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,66 @@ static ResourceId fromPb( } } - public static class Builder { + public static abstract class Builder { + + /** + * Set the user-assigned name of the project. + * + *

This field is optional and can remain unset. Allowed characters are: lowercase and + * uppercase letters, numbers, hyphen, single-quote, double-quote, space, and exclamation point. + * This field can be changed after project creation. + */ + public abstract Builder name(String name); + + /** + * Set the unique, user-assigned ID of the project. + * + *

The ID must be 6 to 30 lowercase letters, digits, or hyphens. It must start with a letter. + * Trailing hyphens are prohibited. This field cannot be changed after the server creates the + * project. + */ + public abstract Builder projectId(String projectId); + + /** + * Add a label associated with this project. + * + *

See {@link #labels} for label restrictions. + */ + public abstract Builder addLabel(String key, String value); + + /** + * Remove a label associated with this project. + */ + public abstract Builder removeLabel(String key); + + /** + * Clear the labels associated with this project. + */ + public abstract Builder clearLabels(); + + /** + * Set the labels associated with this project. + * + *

Label keys must be between 1 and 63 characters long and must conform to the following + * regular expression: [a-z]([-a-z0-9]*[a-z0-9])?. Label values must be between 0 and 63 + * characters long and must conform to the regular expression ([a-z]([-a-z0-9]*[a-z0-9])?)?. No + * more than 256 labels can be associated with a given resource. This field can be changed after + * project creation. + */ + public abstract Builder labels(Map labels); + + abstract Builder projectNumber(Long projectNumber); + + abstract Builder state(State state); + + abstract Builder createTimeMillis(Long createTimeMillis); + + abstract Builder parent(ResourceId parent); + + public abstract ProjectInfo build(); + } + + static class BuilderImpl extends Builder { private String name; private String projectId; @@ -125,10 +184,9 @@ public static class Builder { private Long createTimeMillis; private ResourceId parent; - private Builder() { - } + BuilderImpl() {} - Builder(ProjectInfo info) { + BuilderImpl(ProjectInfo info) { this.name = info.name; this.projectId = info.projectId; this.labels.putAll(info.labels); @@ -138,96 +196,73 @@ private Builder() { this.parent = info.parent; } - /** - * Set the user-assigned name of the project. - * - *

This field is optional and can remain unset. Allowed characters are: lowercase and - * uppercase letters, numbers, hyphen, single-quote, double-quote, space, and exclamation point. - * This field can be changed after project creation. - */ + @Override public Builder name(String name) { this.name = firstNonNull(name, Data.nullOf(String.class)); return this; } - /** - * Set the unique, user-assigned ID of the project. - * - *

The ID must be 6 to 30 lowercase letters, digits, or hyphens. It must start with a letter. - * Trailing hyphens are prohibited. This field cannot be changed after the server creates the - * project. - */ + @Override public Builder projectId(String projectId) { this.projectId = checkNotNull(projectId); return this; } - /** - * Add a label associated with this project. - * - *

See {@link #labels} for label restrictions. - */ + @Override public Builder addLabel(String key, String value) { this.labels.put(key, value); return this; } - /** - * Remove a label associated with this project. - */ + @Override public Builder removeLabel(String key) { this.labels.remove(key); return this; } - /** - * Clear the labels associated with this project. - */ + @Override public Builder clearLabels() { this.labels.clear(); return this; } - /** - * Set the labels associated with this project. - * - *

Label keys must be between 1 and 63 characters long and must conform to the following - * regular expression: [a-z]([-a-z0-9]*[a-z0-9])?. Label values must be between 0 and 63 - * characters long and must conform to the regular expression ([a-z]([-a-z0-9]*[a-z0-9])?)?. No - * more than 256 labels can be associated with a given resource. This field can be changed after - * project creation. - */ + @Override public Builder labels(Map labels) { this.labels = Maps.newHashMap(checkNotNull(labels)); return this; } + @Override Builder projectNumber(Long projectNumber) { this.projectNumber = projectNumber; return this; } + @Override Builder state(State state) { this.state = state; return this; } + @Override Builder createTimeMillis(Long createTimeMillis) { this.createTimeMillis = createTimeMillis; return this; } + @Override Builder parent(ResourceId parent) { this.parent = parent; return this; } + @Override public ProjectInfo build() { return new ProjectInfo(this); } } - ProjectInfo(Builder builder) { + ProjectInfo(BuilderImpl builder) { this.name = builder.name; this.projectId = builder.projectId; this.labels = ImmutableMap.copyOf(builder.labels); @@ -296,7 +331,8 @@ public Long createTimeMillis() { @Override public boolean equals(Object obj) { - return obj instanceof ProjectInfo && Objects.equals(toPb(), ((ProjectInfo) obj).toPb()); + return obj.getClass().equals(ProjectInfo.class) + && Objects.equals(toPb(), ((ProjectInfo) obj).toPb()); } @Override @@ -305,11 +341,11 @@ public int hashCode() { } public static Builder builder(String id) { - return new Builder().projectId(id); + return new BuilderImpl().projectId(id); } public Builder toBuilder() { - return new Builder(this); + return new BuilderImpl(this); } com.google.api.services.cloudresourcemanager.model.Project toPb() { diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java index 3d658d18d28a..af772dce6b60 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManager.java @@ -179,12 +179,12 @@ public static ProjectListOption fields(ProjectField... fields) { * @see Cloud * Resource Manager create - * @return ProjectInfo object representing the new project's metadata. The returned object will + * @return Project object representing the new project's metadata. The returned object will * include the following read-only fields supplied by the server: project number, lifecycle * state, and creation time. * @throws ResourceManagerException upon failure */ - ProjectInfo create(ProjectInfo project); + Project create(ProjectInfo project); /** * Marks the project identified by the specified project ID for deletion. @@ -221,7 +221,7 @@ public static ProjectListOption fields(ProjectField... fields) { * Resource Manager get * @throws ResourceManagerException upon failure */ - ProjectInfo get(String projectId, ProjectGetOption... options); + Project get(String projectId, ProjectGetOption... options); /** * Lists the projects visible to the current user. @@ -234,10 +234,10 @@ public static ProjectListOption fields(ProjectField... fields) { * @see Cloud * Resource Manager list - * @return {@code Page}, a page of projects + * @return {@code Page}, a page of projects * @throws ResourceManagerException upon failure */ - Page list(ProjectListOption... options); + Page list(ProjectListOption... options); /** * Replaces the attributes of the project. @@ -247,10 +247,10 @@ public static ProjectListOption fields(ProjectField... fields) { * @see Cloud * Resource Manager update - * @return the ProjectInfo representing the new project metadata + * @return the Project representing the new project metadata * @throws ResourceManagerException upon failure */ - ProjectInfo replace(ProjectInfo newProject); + Project replace(ProjectInfo newProject); /** * Restores the project identified by the specified project ID. diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java index 22f2b350d2f3..e087caab5966 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/ResourceManagerImpl.java @@ -46,9 +46,9 @@ final class ResourceManagerImpl } @Override - public ProjectInfo create(final ProjectInfo project) { + public Project create(final ProjectInfo project) { try { - return ProjectInfo.fromPb(runWithRetries( + return Project.fromPb(this, runWithRetries( new Callable() { @Override public com.google.api.services.cloudresourcemanager.model.Project call() { @@ -76,7 +76,7 @@ public Void call() { } @Override - public ProjectInfo get(final String projectId, ProjectGetOption... options) { + public Project get(final String projectId, ProjectGetOption... options) { final Map optionsMap = optionMap(options); try { com.google.api.services.cloudresourcemanager.model.Project answer = runWithRetries( @@ -86,13 +86,13 @@ public com.google.api.services.cloudresourcemanager.model.Project call() { return resourceManagerRpc.get(projectId, optionsMap); } }, options().retryParams(), EXCEPTION_HANDLER); - return answer == null ? null : ProjectInfo.fromPb(answer); + return answer == null ? null : Project.fromPb(this, answer); } catch (RetryHelperException e) { throw ResourceManagerException.translateAndThrow(e); } } - private static class ProjectPageFetcher implements NextPageFetcher { + private static class ProjectPageFetcher implements NextPageFetcher { private static final long serialVersionUID = 2158209410430566961L; private final Map requestOptions; @@ -106,17 +106,17 @@ private static class ProjectPageFetcher implements NextPageFetcher } @Override - public Page nextPage() { + public Page nextPage() { return listProjects(serviceOptions, requestOptions); } } @Override - public Page list(ProjectListOption... options) { + public Page list(ProjectListOption... options) { return listProjects(options(), optionMap(options)); } - private static Page listProjects(final ResourceManagerOptions serviceOptions, + private static Page listProjects(final ResourceManagerOptions serviceOptions, final Map optionsMap) { try { Tuple> result = @@ -130,16 +130,17 @@ Iterable> call() { }, serviceOptions.retryParams(), EXCEPTION_HANDLER); String cursor = result.x(); - Iterable projects = + Iterable projects = result.y() == null - ? ImmutableList.of() : Iterables.transform( + ? ImmutableList.of() : Iterables.transform( result.y(), new Function() { + Project>() { @Override - public ProjectInfo apply( + public Project apply( com.google.api.services.cloudresourcemanager.model.Project projectPb) { - return ProjectInfo.fromPb(projectPb); + return new Project( + serviceOptions.service(), new ProjectInfo.BuilderImpl(ProjectInfo.fromPb(projectPb))); } }); return new PageImpl<>( @@ -150,9 +151,9 @@ public ProjectInfo apply( } @Override - public ProjectInfo replace(final ProjectInfo newProject) { + public Project replace(final ProjectInfo newProject) { try { - return ProjectInfo.fromPb(runWithRetries( + return Project.fromPb(this, runWithRetries( new Callable() { @Override public com.google.api.services.cloudresourcemanager.model.Project call() { diff --git a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/package-info.java b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/package-info.java index b8687fbf1314..22a81499eb7a 100644 --- a/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/package-info.java +++ b/gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/package-info.java @@ -21,10 +21,12 @@ *

 {@code
  * ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service();
  * String myProjectId = "my-globally-unique-project-id"; // Change to a unique project ID.
- * ProjectInfo myProject = resourceManager.create(ProjectInfo.builder(myProjectId).build());
- * ProjectInfo newProjectInfo = resourceManager.replace(myProject.toBuilder()
- *     .addLabel("launch-status", "in-development").build());
- * Iterator projectIterator = resourceManager.list().iterateAll();
+ * Project myProject = resourceManager.create(ProjectInfo.builder(myProjectId).build());
+ * Project newProject = myProject.toBuilder()
+ *     .addLabel("launch-status", "in-development")
+ *     .build()
+ *     .replace();
+ * Iterator projectIterator = resourceManager.list().iterateAll();
  * System.out.println("Projects I can view:");
  * while (projectIterator.hasNext()) {
  *   System.out.println(projectIterator.next().projectId());
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 077818bf2bb9..a741963913c6 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
@@ -16,19 +16,19 @@
 
 package com.google.gcloud.resourcemanager;
 
+import static org.easymock.EasyMock.anyObject;
+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.assertNotNull;
 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;
@@ -48,89 +48,152 @@ public class ProjectTest {
       .state(STATE)
       .build();
 
+  private ResourceManager serviceMockReturnsOptions = createStrictMock(ResourceManager.class);
+  private ResourceManagerOptions mockOptions = createMock(ResourceManagerOptions.class);
   private ResourceManager resourceManager;
+  private Project expectedProject;
   private Project project;
 
-  @Before
-  public void setUp() throws Exception {
-    resourceManager = createStrictMock(ResourceManager.class);
-    project = new Project(resourceManager, PROJECT_INFO);
-  }
-
   @After
   public void tearDown() throws Exception {
     verify(resourceManager);
   }
 
+  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));
+  }
+
+  private void initializeProject() {
+    project = new Project(resourceManager, new ProjectInfo.BuilderImpl(PROJECT_INFO));
+  }
+
+  @Test
+  public void testBuilder() {
+    initializeExpectedProject(2);
+    replay(resourceManager);
+    Project builtProject = Project.builder(serviceMockReturnsOptions, PROJECT_ID)
+        .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());
+  }
+
   @Test
-  public void testLoad() {
-    expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(PROJECT_INFO);
+  public void testGet() {
+    initializeExpectedProject(1);
+    expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(expectedProject);
     replay(resourceManager);
     Project loadedProject = Project.get(resourceManager, PROJECT_INFO.projectId());
-    assertEquals(PROJECT_INFO, loadedProject.info());
+    assertEquals(expectedProject, loadedProject);
   }
 
   @Test
   public void testReload() {
+    initializeExpectedProject(2);
     ProjectInfo newInfo = PROJECT_INFO.toBuilder().addLabel("k3", "v3").build();
-    expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(newInfo);
+    Project expectedProject =
+        new Project(serviceMockReturnsOptions, new ProjectInfo.BuilderImpl(newInfo));
+    expect(resourceManager.options()).andReturn(mockOptions);
+    expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(expectedProject);
     replay(resourceManager);
+    initializeProject();
     Project newProject = project.reload();
-    assertSame(resourceManager, newProject.resourceManager());
-    assertEquals(newInfo, newProject.info());
+    assertEquals(expectedProject, newProject);
   }
 
   @Test
   public void testLoadNull() {
+    initializeExpectedProject(1);
     expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(null);
     replay(resourceManager);
     assertNull(Project.get(resourceManager, PROJECT_INFO.projectId()));
   }
 
   @Test
-  public void testReloadDeletedProject() {
-    expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(PROJECT_INFO);
+  public void testReloadNull() {
+    initializeExpectedProject(1);
+    expect(resourceManager.options()).andReturn(mockOptions);
     expect(resourceManager.get(PROJECT_INFO.projectId())).andReturn(null);
     replay(resourceManager);
-    Project loadedProject = Project.get(resourceManager, PROJECT_INFO.projectId());
-    assertNotNull(loadedProject);
-    Project reloadedProject = loadedProject.reload();
+    Project reloadedProject =
+        new Project(resourceManager, new ProjectInfo.BuilderImpl(PROJECT_INFO)).reload();
     assertNull(reloadedProject);
   }
 
-  @Test
-  public void testInfo() {
-    replay(resourceManager);
-    assertEquals(PROJECT_INFO, project.info());
-  }
-
   @Test
   public void testResourceManager() {
+    initializeExpectedProject(1);
     replay(resourceManager);
-    assertEquals(resourceManager, project.resourceManager());
+    assertEquals(serviceMockReturnsOptions, expectedProject.resourceManager());
   }
 
   @Test
   public void testDelete() {
+    initializeExpectedProject(1);
+    expect(resourceManager.options()).andReturn(mockOptions);
     resourceManager.delete(PROJECT_INFO.projectId());
     replay(resourceManager);
+    initializeProject();
     project.delete();
   }
 
   @Test
   public void testUndelete() {
+    initializeExpectedProject(1);
+    expect(resourceManager.options()).andReturn(mockOptions);
     resourceManager.undelete(PROJECT_INFO.projectId());
     replay(resourceManager);
+    initializeProject();
     project.undelete();
   }
 
   @Test
   public void testReplace() {
-    ProjectInfo newInfo = PROJECT_INFO.toBuilder().addLabel("k3", "v3").build();
-    expect(resourceManager.replace(newInfo)).andReturn(newInfo);
+    initializeExpectedProject(2);
+    Project expectedReplacedProject = expectedProject.toBuilder().addLabel("k3", "v3").build();
+    expect(resourceManager.options()).andReturn(mockOptions).times(2);
+    expect(resourceManager.replace(anyObject(Project.class))).andReturn(expectedReplacedProject);
     replay(resourceManager);
-    Project newProject = project.replace(newInfo);
-    assertSame(resourceManager, newProject.resourceManager());
-    assertEquals(newInfo, newProject.info());
+    initializeProject();
+    Project newProject =
+        new Project(resourceManager, new ProjectInfo.BuilderImpl(expectedReplacedProject));
+    Project actualReplacedProject = newProject.replace();
+    compareProjectInfos(expectedReplacedProject, actualReplacedProject);
+  }
+
+  private void compareProjects(Project expected, Project value) {
+    assertEquals(expected, value);
+    compareProjectInfos(expected, value);
+    assertEquals(expected.resourceManager().options(), value.resourceManager().options());
+  }
+
+  private void compareProjectInfos(ProjectInfo expected, ProjectInfo value) {
+    assertEquals(expected.projectId(), value.projectId());
+    assertEquals(expected.name(), value.name());
+    assertEquals(expected.labels(), value.labels());
+    assertEquals(expected.projectNumber(), value.projectNumber());
+    assertEquals(expected.createTimeMillis(), value.createTimeMillis());
+    assertEquals(expected.state(), value.state());
+    assertEquals(expected.parent(), value.parent());
   }
 }
diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java
index 7d1e00496463..37c54718fb4a 100644
--- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java
+++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/ResourceManagerImplTest.java
@@ -20,6 +20,7 @@
 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 static org.junit.Assert.fail;
 
@@ -78,7 +79,7 @@ public void setUp() {
   }
 
   private void clearProjects() {
-    for (ProjectInfo project : RESOURCE_MANAGER.list().values()) {
+    for (Project project : RESOURCE_MANAGER.list().values()) {
       RESOURCE_MANAGER_HELPER.removeProject(project.projectId());
     }
   }
@@ -97,13 +98,14 @@ private void compareReadWriteFields(ProjectInfo expected, ProjectInfo actual) {
 
   @Test
   public void testCreate() {
-    ProjectInfo returnedProject = RESOURCE_MANAGER.create(PARTIAL_PROJECT);
+    Project returnedProject = RESOURCE_MANAGER.create(PARTIAL_PROJECT);
     compareReadWriteFields(PARTIAL_PROJECT, returnedProject);
     assertEquals(ProjectInfo.State.ACTIVE, returnedProject.state());
     assertNull(returnedProject.name());
     assertNull(returnedProject.parent());
     assertNotNull(returnedProject.projectNumber());
     assertNotNull(returnedProject.createTimeMillis());
+    assertSame(RESOURCE_MANAGER, returnedProject.resourceManager());
     try {
       RESOURCE_MANAGER.create(PARTIAL_PROJECT);
       fail("Should fail, project already exists.");
@@ -117,6 +119,7 @@ public void testCreate() {
     assertEquals(ProjectInfo.State.ACTIVE, returnedProject.state());
     assertNotNull(returnedProject.projectNumber());
     assertNotNull(returnedProject.createTimeMillis());
+    assertSame(RESOURCE_MANAGER, returnedProject.resourceManager());
   }
 
   @Test
@@ -137,16 +140,17 @@ public void testDelete() {
   @Test
   public void testGet() {
     RESOURCE_MANAGER.create(COMPLETE_PROJECT);
-    ProjectInfo returnedProject = RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId());
+    Project returnedProject = RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId());
     compareReadWriteFields(COMPLETE_PROJECT, returnedProject);
+    assertEquals(RESOURCE_MANAGER, returnedProject.resourceManager());
     RESOURCE_MANAGER_HELPER.removeProject(COMPLETE_PROJECT.projectId());
     assertNull(RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId()));
   }
 
   @Test
   public void testGetWithOptions() {
-    ProjectInfo originalProject = RESOURCE_MANAGER.create(COMPLETE_PROJECT);
-    ProjectInfo returnedProject = RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId(), GET_FIELDS);
+    Project originalProject = RESOURCE_MANAGER.create(COMPLETE_PROJECT);
+    Project returnedProject = RESOURCE_MANAGER.get(COMPLETE_PROJECT.projectId(), GET_FIELDS);
     assertFalse(COMPLETE_PROJECT.equals(returnedProject));
     assertEquals(COMPLETE_PROJECT.projectId(), returnedProject.projectId());
     assertEquals(COMPLETE_PROJECT.name(), returnedProject.name());
@@ -155,15 +159,17 @@ public void testGetWithOptions() {
     assertNull(returnedProject.projectNumber());
     assertNull(returnedProject.state());
     assertTrue(returnedProject.labels().isEmpty());
+    assertEquals(RESOURCE_MANAGER, originalProject.resourceManager());
+    assertEquals(RESOURCE_MANAGER, returnedProject.resourceManager());
   }
 
   @Test
   public void testList() {
-    Page projects = RESOURCE_MANAGER.list();
+    Page projects = RESOURCE_MANAGER.list();
     assertFalse(projects.values().iterator().hasNext()); // TODO: change this when #421 is resolved
     RESOURCE_MANAGER.create(PARTIAL_PROJECT);
     RESOURCE_MANAGER.create(COMPLETE_PROJECT);
-    for (ProjectInfo p : RESOURCE_MANAGER.list().values()) {
+    for (Project p : RESOURCE_MANAGER.list().values()) {
       if (p.projectId().equals(PARTIAL_PROJECT.projectId())) {
         compareReadWriteFields(PARTIAL_PROJECT, p);
       } else if (p.projectId().equals(COMPLETE_PROJECT.projectId())) {
@@ -171,14 +177,15 @@ public void testList() {
       } else {
         fail("Some unexpected project returned by list.");
       }
+      assertSame(RESOURCE_MANAGER, p.resourceManager());
     }
   }
 
   @Test
   public void testListFieldOptions() {
     RESOURCE_MANAGER.create(COMPLETE_PROJECT);
-    Page projects = RESOURCE_MANAGER.list(LIST_FIELDS);
-    ProjectInfo returnedProject = projects.iterateAll().next();
+    Page projects = RESOURCE_MANAGER.list(LIST_FIELDS);
+    Project returnedProject = projects.iterateAll().next();
     assertEquals(COMPLETE_PROJECT.projectId(), returnedProject.projectId());
     assertEquals(COMPLETE_PROJECT.name(), returnedProject.name());
     assertEquals(COMPLETE_PROJECT.labels(), returnedProject.labels());
@@ -186,6 +193,7 @@ public void testListFieldOptions() {
     assertNull(returnedProject.projectNumber());
     assertNull(returnedProject.state());
     assertNull(returnedProject.createTimeMillis());
+    assertSame(RESOURCE_MANAGER, returnedProject.resourceManager());
   }
 
   @Test
@@ -207,10 +215,11 @@ public void testListFilterOptions() {
     RESOURCE_MANAGER.create(nonMatchingProject1);
     RESOURCE_MANAGER.create(nonMatchingProject2);
     RESOURCE_MANAGER.create(nonMatchingProject3);
-    for (ProjectInfo p : RESOURCE_MANAGER.list(LIST_FILTER).values()) {
+    for (Project p : RESOURCE_MANAGER.list(LIST_FILTER).values()) {
       assertFalse(p.equals(nonMatchingProject1));
       assertFalse(p.equals(nonMatchingProject2));
       compareReadWriteFields(matchingProject, p);
+      assertSame(RESOURCE_MANAGER, p.resourceManager());
     }
   }
 
@@ -225,11 +234,12 @@ public void testReplace() {
         .state(ProjectInfo.State.DELETE_REQUESTED)
         .parent(createdProject.parent())
         .build();
-    ProjectInfo returnedProject = RESOURCE_MANAGER.replace(anotherCompleteProject);
+    Project returnedProject = RESOURCE_MANAGER.replace(anotherCompleteProject);
     compareReadWriteFields(anotherCompleteProject, returnedProject);
     assertEquals(createdProject.projectNumber(), returnedProject.projectNumber());
     assertEquals(createdProject.createTimeMillis(), returnedProject.createTimeMillis());
     assertEquals(createdProject.state(), returnedProject.state());
+    assertEquals(RESOURCE_MANAGER, returnedProject.resourceManager());
     ProjectInfo nonexistantProject =
         ProjectInfo.builder("some-project-id-that-does-not-exist").build();
     try {
@@ -276,8 +286,10 @@ public void testRetryableException() {
         .andThrow(new ResourceManagerException(500, "Internal Error"))
         .andReturn(PARTIAL_PROJECT.toPb());
     EasyMock.replay(resourceManagerRpcMock);
-    ProjectInfo returnedProject = resourceManagerMock.get(PARTIAL_PROJECT.projectId());
-    assertEquals(PARTIAL_PROJECT, returnedProject);
+    Project returnedProject = resourceManagerMock.get(PARTIAL_PROJECT.projectId());
+    assertEquals(
+        new Project(resourceManagerMock, new ProjectInfo.BuilderImpl(PARTIAL_PROJECT)),
+        returnedProject);
   }
 
   @Test
diff --git a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java
index 64e09449149b..497de880254a 100644
--- a/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java
+++ b/gcloud-java-resourcemanager/src/test/java/com/google/gcloud/resourcemanager/SerializationTest.java
@@ -35,6 +35,8 @@
 
 public class SerializationTest {
 
+private static final ResourceManager RESOURCE_MANAGER =
+      ResourceManagerOptions.defaultInstance().service();
   private static final ProjectInfo PARTIAL_PROJECT_INFO = ProjectInfo.builder("id1").build();
   private static final ProjectInfo FULL_PROJECT_INFO = ProjectInfo.builder("id")
       .name("name")
@@ -43,8 +45,10 @@ public class SerializationTest {
       .state(ProjectInfo.State.ACTIVE)
       .createTimeMillis(1234L)
       .build();
-  private static final PageImpl PAGE_RESULT =
-      new PageImpl<>(null, "c", Collections.singletonList(PARTIAL_PROJECT_INFO));
+  private static final Project PROJECT =
+      new Project(RESOURCE_MANAGER, new ProjectInfo.BuilderImpl(FULL_PROJECT_INFO));
+  private static final PageImpl PAGE_RESULT =
+      new PageImpl<>(null, "c", Collections.singletonList(PROJECT));
   private static final ResourceManager.ProjectGetOption PROJECT_GET_OPTION =
       ResourceManager.ProjectGetOption.fields(ResourceManager.ProjectField.NAME);
   private static final ResourceManager.ProjectListOption PROJECT_LIST_OPTION =
@@ -65,7 +69,7 @@ public void testServiceOptions() throws Exception {
 
   @Test
   public void testModelAndRequests() throws Exception {
-    Serializable[] objects = {PARTIAL_PROJECT_INFO, FULL_PROJECT_INFO, PAGE_RESULT,
+    Serializable[] objects = {PARTIAL_PROJECT_INFO, FULL_PROJECT_INFO, PROJECT, PAGE_RESULT,
         PROJECT_GET_OPTION, PROJECT_LIST_OPTION};
     for (Serializable obj : objects) {
       Object copy = serializeAndDeserialize(obj);