Skip to content

Firestore API #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Nov 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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 -->
Expand All @@ -367,7 +372,7 @@
<version>1.7.25</version>
</dependency>

<!-- Test dependencies -->
<!-- Test Dependencies -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/com/google/firebase/FirebaseOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ public Map<String, Object> getDatabaseAuthVariableOverride() {
return databaseAuthVariableOverride;
}

/**
* Returns the Google Cloud project ID.
*
* @return The project ID set via {@link Builder#setProjectId(String)}
*/
public String getProjectId() {
return projectId;
}
Expand Down Expand Up @@ -251,7 +256,13 @@ public Builder setDatabaseAuthVariableOverride(
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
91 changes: 91 additions & 0 deletions src/main/java/com/google/firebase/cloud/FirestoreClient.java
Original file line number Diff line number Diff line change
@@ -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.
}
}

}
39 changes: 39 additions & 0 deletions src/test/java/com/google/firebase/cloud/FirestoreClientIT.java
Original file line number Diff line number Diff line change
@@ -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();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to add instructions to CONTRIBUTING.md (or wherever) to open up your Firestore security rules?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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());
}
}
73 changes: 73 additions & 0 deletions src/test/java/com/google/firebase/cloud/FirestoreClientTest.java
Original file line number Diff line number Diff line change
@@ -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
}
}

}