Repository: firebase/firebase-admin-java
Pull request #83 (2017-10-06 22:13:12)

Firestore API
Usage:

```
Firestore fs = FirestoreClient.getFirestore();
```

hiranya911: @mikelehen Code is pretty much the same as it was when you reviewed a while ago. It has just been updated/merged with the other changes that has been done recently in the master branch.
hiranya911: Made the suggested changes.

Patch:
diff --git a/pom.xml b/pom.xml
index 78a9263..04521d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -347,7 +347,12 @@
         <dependency>
             <groupId>com.google.cloud</groupId>
             <artifactId>google-cloud-storage</artifactId>
-            <version>1.2.1</version>
+            <version>1.7.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.cloud</groupId>
+            <artifactId>google-cloud-firestore</artifactId>
+            <version>0.25.0-beta</version>
         </dependency>
 
         <!-- Utilities -->
@@ -367,7 +372,7 @@
             <version>1.7.25</version>
         </dependency>
 
-        <!-- Test dependencies -->
+        <!-- Test Dependencies -->
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
diff --git a/src/main/java/com/google/firebase/FirebaseOptions.java b/src/main/java/com/google/firebase/FirebaseOptions.java
index 52c8131..30f05ca 100644
--- a/src/main/java/com/google/firebase/FirebaseOptions.java
+++ b/src/main/java/com/google/firebase/FirebaseOptions.java
@@ -97,6 +97,11 @@ public final class FirebaseOptions {
     return databaseAuthVariableOverride;
   }
 
+  /**
+   * Returns the Google Cloud project ID.
+   *
+   * @return The project ID set via {@link Builder#setProjectId(String)}
+   */
   public String getProjectId() {
     return projectId;
   }
@@ -251,7 +256,13 @@ public final class FirebaseOptions {
       return this;
     }
 
-    public Builder setProjectId(String projectId) {
+    /**
+     * Sets the Google Cloud project ID that should be associated with an app.
+     *
+     * @param projectId A non-null, non-empty project ID string.
+     * @return This <code>Builder</code> instance is returned so subsequent calls can be chained.
+     */
+    public Builder setProjectId(@NonNull String projectId) {
       checkArgument(!Strings.isNullOrEmpty(projectId), "Project ID must not be null or empty");
       this.projectId = projectId;
       return this;
diff --git a/src/main/java/com/google/firebase/auth/internal/BaseCredential.java b/src/main/java/com/google/firebase/auth/internal/BaseCredential.java
index 2e717ad..d15832a 100644
--- a/src/main/java/com/google/firebase/auth/internal/BaseCredential.java
+++ b/src/main/java/com/google/firebase/auth/internal/BaseCredential.java
@@ -46,7 +46,11 @@ public abstract class BaseCredential implements FirebaseCredential {
           "https://www.googleapis.com/auth/identitytoolkit",
 
           // Enables access to Google Cloud Storage.
-          "https://www.googleapis.com/auth/devstorage.full_control");
+          "https://www.googleapis.com/auth/devstorage.full_control",
+
+          // Enables access to Google Cloud Firestore
+          "https://www.googleapis.com/auth/cloud-platform",
+          "https://www.googleapis.com/auth/datastore");
 
   private final GoogleCredentials googleCredentials;
 
diff --git a/src/main/java/com/google/firebase/cloud/FirestoreClient.java b/src/main/java/com/google/firebase/cloud/FirestoreClient.java
new file mode 100644
index 0000000..a3d0420
--- /dev/null
+++ b/src/main/java/com/google/firebase/cloud/FirestoreClient.java
@@ -0,0 +1,91 @@
+package com.google.firebase.cloud;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.cloud.firestore.Firestore;
+import com.google.cloud.firestore.FirestoreOptions;
+import com.google.common.base.Strings;
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.ImplFirebaseTrampolines;
+import com.google.firebase.internal.FirebaseService;
+import com.google.firebase.internal.NonNull;
+
+/**
+ * FirestoreClient provides access to Google Cloud Firestore. Use this API to obtain a
+ * <code>com.google.cloud.firestore.Firestore</code> instance, which provides methods for
+ * updating and querying data in Firestore.
+ *
+ * <p>A Google Cloud project ID is required to access Firestore. FirestoreClient determines the
+ * project ID from the {@link com.google.firebase.FirebaseOptions} used to initialize the underlying
+ * {@link FirebaseApp}. If that is not available, it examines the credentials used to initialize
+ * the app. Finally it attempts to get the project ID by looking up the GCLOUD_PROJECT environment
+ * variable. If a project ID cannot be determined by any of these methods, this API will throw
+ * a runtime exception.
+ */
+public class FirestoreClient {
+
+  private final Firestore firestore;
+
+  private FirestoreClient(FirebaseApp app) {
+    checkNotNull(app, "FirebaseApp must not be null");
+    String projectId = ImplFirebaseTrampolines.getProjectId(app);
+    checkArgument(!Strings.isNullOrEmpty(projectId),
+        "Project ID is required for accessing Firestore. Use a service account credential or "
+            + "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
+            + "set the project ID via the GCLOUD_PROJECT environment variable.");
+    this.firestore = FirestoreOptions.newBuilder()
+        .setCredentials(ImplFirebaseTrampolines.getCredentials(app))
+        .setProjectId(projectId)
+        .build()
+        .getService();
+  }
+
+  /**
+   * Returns the Firestore instance associated with the default Firebase app.
+   *
+   * @return A non-null <code>com.google.cloud.firestore.Firestore</code> instance.
+   */
+  @NonNull
+  public static Firestore getFirestore() {
+    return getFirestore(FirebaseApp.getInstance());
+  }
+
+  /**
+   * Returns the Firestore instance associated with the specified Firebase app.
+   *
+   * @param app A non-null {@link FirebaseApp}.
+   * @return A non-null <code>com.google.cloud.firestore.Firestore</code> instance.
+   */
+  @NonNull
+  public static Firestore getFirestore(FirebaseApp app) {
+    return getInstance(app).firestore;
+  }
+
+  private static synchronized FirestoreClient getInstance(FirebaseApp app) {
+    FirestoreClientService service = ImplFirebaseTrampolines.getService(app,
+        SERVICE_ID, FirestoreClientService.class);
+    if (service == null) {
+      service = ImplFirebaseTrampolines.addService(app, new FirestoreClientService(app));
+    }
+    return service.getInstance();
+  }
+
+  private static final String SERVICE_ID = FirestoreClient.class.getName();
+
+  private static class FirestoreClientService extends FirebaseService<FirestoreClient> {
+
+    FirestoreClientService(FirebaseApp app) {
+      super(SERVICE_ID, new FirestoreClient(app));
+    }
+
+    @Override
+    public void destroy() {
+      // NOTE: We don't explicitly tear down anything here (for now). User won't be able to call
+      // FirestoreClient.getFirestore() any more, but already created Firestore instances will
+      // continue to work. Request Firestore team to provide a cleanup/teardown method on the
+      // Firestore object.
+    }
+  }
+
+}


Test patch:
diff --git a/src/test/java/com/google/firebase/cloud/FirestoreClientIT.java b/src/test/java/com/google/firebase/cloud/FirestoreClientIT.java
new file mode 100644
index 0000000..72c832c
--- /dev/null
+++ b/src/test/java/com/google/firebase/cloud/FirestoreClientIT.java
@@ -0,0 +1,39 @@
+package com.google.firebase.cloud;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import com.google.cloud.firestore.DocumentReference;
+import com.google.cloud.firestore.Firestore;
+import com.google.cloud.firestore.WriteResult;
+import com.google.common.collect.ImmutableMap;
+import com.google.firebase.testing.IntegrationTestUtils;
+import java.util.Map;
+import org.junit.Test;
+
+public class FirestoreClientIT {
+
+  @Test
+  public void testFirestoreAccess() throws Exception {
+    Firestore firestore = FirestoreClient.getFirestore(IntegrationTestUtils.ensureDefaultApp());
+    DocumentReference reference = firestore.collection("cities").document("Mountain View");
+    ImmutableMap<String, Object> expected = ImmutableMap.<String, Object>of(
+        "name", "Mountain View",
+        "country", "USA",
+        "population", 77846L,
+        "capital", false
+    );
+    WriteResult result = reference.set(expected).get();
+    assertNotNull(result);
+
+    Map<String, Object> data = reference.get().get().getData();
+    assertEquals(expected.size(), data.size());
+    for (Map.Entry<String, Object> entry : expected.entrySet()) {
+      assertEquals(entry.getValue(), data.get(entry.getKey()));
+    }
+
+    reference.delete().get();
+    assertFalse(reference.get().get().exists());
+  }
+}
diff --git a/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java b/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java
new file mode 100644
index 0000000..737eab1
--- /dev/null
+++ b/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java
@@ -0,0 +1,73 @@
+package com.google.firebase.cloud;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.cloud.firestore.Firestore;
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.FirebaseOptions;
+import com.google.firebase.TestOnlyImplFirebaseTrampolines;
+import com.google.firebase.testing.ServiceAccount;
+import java.io.IOException;
+import org.junit.After;
+import org.junit.Test;
+
+public class FirestoreClientTest {
+
+  @After
+  public void tearDown() {
+    TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+  }
+
+  @Test
+  public void testExplicitProjectId() throws IOException {
+    FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+        .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
+        .setProjectId("explicit-project-id")
+        .build());
+    Firestore firestore = FirestoreClient.getFirestore(app);
+    assertEquals("explicit-project-id", firestore.getOptions().getProjectId());
+
+    firestore = FirestoreClient.getFirestore();
+    assertEquals("explicit-project-id", firestore.getOptions().getProjectId());
+  }
+
+  @Test
+  public void testServiceAccountProjectId() throws IOException {
+    FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+        .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
+        .build());
+    Firestore firestore = FirestoreClient.getFirestore(app);
+    assertEquals("mock-project-id", firestore.getOptions().getProjectId());
+
+    firestore = FirestoreClient.getFirestore();
+    assertEquals("mock-project-id", firestore.getOptions().getProjectId());
+  }
+
+  @Test
+  public void testAppDelete() throws IOException {
+    FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+        .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
+        .setProjectId("mock-project-id")
+        .build());
+
+    assertNotNull(FirestoreClient.getFirestore(app));
+    app.delete();
+    try {
+      FirestoreClient.getFirestore(app);
+      fail("No error thrown for deleted app");
+    } catch (IllegalStateException expected) {
+      // ignore
+    }
+
+    try {
+      FirestoreClient.getFirestore();
+      fail("No error thrown for deleted app");
+    } catch (IllegalStateException expected) {
+      // ignore
+    }
+  }
+
+}