Skip to content

Commit 202c332

Browse files
authored
Firestore API (#83)
* Prototyping Firestore integration for Java * Some code cleanup * Updating comments * Code cleanup * Removed getInstance() methods from FirestoreClient and added getFirestore() methods. * Minor cleanup * Adding Firestore OAuth scopes * Some documentation updates * Reverted a doc change * Updating GCS dependency version (#89)
1 parent e35216d commit 202c332

File tree

6 files changed

+227
-4
lines changed

6 files changed

+227
-4
lines changed

pom.xml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,12 @@
347347
<dependency>
348348
<groupId>com.google.cloud</groupId>
349349
<artifactId>google-cloud-storage</artifactId>
350-
<version>1.2.1</version>
350+
<version>1.7.0</version>
351+
</dependency>
352+
<dependency>
353+
<groupId>com.google.cloud</groupId>
354+
<artifactId>google-cloud-firestore</artifactId>
355+
<version>0.25.0-beta</version>
351356
</dependency>
352357

353358
<!-- Utilities -->
@@ -367,7 +372,7 @@
367372
<version>1.7.25</version>
368373
</dependency>
369374

370-
<!-- Test dependencies -->
375+
<!-- Test Dependencies -->
371376
<dependency>
372377
<groupId>org.mockito</groupId>
373378
<artifactId>mockito-core</artifactId>

src/main/java/com/google/firebase/FirebaseOptions.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ public Map<String, Object> getDatabaseAuthVariableOverride() {
9797
return databaseAuthVariableOverride;
9898
}
9999

100+
/**
101+
* Returns the Google Cloud project ID.
102+
*
103+
* @return The project ID set via {@link Builder#setProjectId(String)}
104+
*/
100105
public String getProjectId() {
101106
return projectId;
102107
}
@@ -251,7 +256,13 @@ public Builder setDatabaseAuthVariableOverride(
251256
return this;
252257
}
253258

254-
public Builder setProjectId(String projectId) {
259+
/**
260+
* Sets the Google Cloud project ID that should be associated with an app.
261+
*
262+
* @param projectId A non-null, non-empty project ID string.
263+
* @return This <code>Builder</code> instance is returned so subsequent calls can be chained.
264+
*/
265+
public Builder setProjectId(@NonNull String projectId) {
255266
checkArgument(!Strings.isNullOrEmpty(projectId), "Project ID must not be null or empty");
256267
this.projectId = projectId;
257268
return this;

src/main/java/com/google/firebase/auth/internal/BaseCredential.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ public abstract class BaseCredential implements FirebaseCredential {
4646
"https://www.googleapis.com/auth/identitytoolkit",
4747

4848
// Enables access to Google Cloud Storage.
49-
"https://www.googleapis.com/auth/devstorage.full_control");
49+
"https://www.googleapis.com/auth/devstorage.full_control",
50+
51+
// Enables access to Google Cloud Firestore
52+
"https://www.googleapis.com/auth/cloud-platform",
53+
"https://www.googleapis.com/auth/datastore");
5054

5155
private final GoogleCredentials googleCredentials;
5256

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.google.firebase.cloud;
2+
3+
import static com.google.common.base.Preconditions.checkArgument;
4+
import static com.google.common.base.Preconditions.checkNotNull;
5+
6+
import com.google.cloud.firestore.Firestore;
7+
import com.google.cloud.firestore.FirestoreOptions;
8+
import com.google.common.base.Strings;
9+
import com.google.firebase.FirebaseApp;
10+
import com.google.firebase.ImplFirebaseTrampolines;
11+
import com.google.firebase.internal.FirebaseService;
12+
import com.google.firebase.internal.NonNull;
13+
14+
/**
15+
* FirestoreClient provides access to Google Cloud Firestore. Use this API to obtain a
16+
* <code>com.google.cloud.firestore.Firestore</code> instance, which provides methods for
17+
* updating and querying data in Firestore.
18+
*
19+
* <p>A Google Cloud project ID is required to access Firestore. FirestoreClient determines the
20+
* project ID from the {@link com.google.firebase.FirebaseOptions} used to initialize the underlying
21+
* {@link FirebaseApp}. If that is not available, it examines the credentials used to initialize
22+
* the app. Finally it attempts to get the project ID by looking up the GCLOUD_PROJECT environment
23+
* variable. If a project ID cannot be determined by any of these methods, this API will throw
24+
* a runtime exception.
25+
*/
26+
public class FirestoreClient {
27+
28+
private final Firestore firestore;
29+
30+
private FirestoreClient(FirebaseApp app) {
31+
checkNotNull(app, "FirebaseApp must not be null");
32+
String projectId = ImplFirebaseTrampolines.getProjectId(app);
33+
checkArgument(!Strings.isNullOrEmpty(projectId),
34+
"Project ID is required for accessing Firestore. Use a service account credential or "
35+
+ "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
36+
+ "set the project ID via the GCLOUD_PROJECT environment variable.");
37+
this.firestore = FirestoreOptions.newBuilder()
38+
.setCredentials(ImplFirebaseTrampolines.getCredentials(app))
39+
.setProjectId(projectId)
40+
.build()
41+
.getService();
42+
}
43+
44+
/**
45+
* Returns the Firestore instance associated with the default Firebase app.
46+
*
47+
* @return A non-null <code>com.google.cloud.firestore.Firestore</code> instance.
48+
*/
49+
@NonNull
50+
public static Firestore getFirestore() {
51+
return getFirestore(FirebaseApp.getInstance());
52+
}
53+
54+
/**
55+
* Returns the Firestore instance associated with the specified Firebase app.
56+
*
57+
* @param app A non-null {@link FirebaseApp}.
58+
* @return A non-null <code>com.google.cloud.firestore.Firestore</code> instance.
59+
*/
60+
@NonNull
61+
public static Firestore getFirestore(FirebaseApp app) {
62+
return getInstance(app).firestore;
63+
}
64+
65+
private static synchronized FirestoreClient getInstance(FirebaseApp app) {
66+
FirestoreClientService service = ImplFirebaseTrampolines.getService(app,
67+
SERVICE_ID, FirestoreClientService.class);
68+
if (service == null) {
69+
service = ImplFirebaseTrampolines.addService(app, new FirestoreClientService(app));
70+
}
71+
return service.getInstance();
72+
}
73+
74+
private static final String SERVICE_ID = FirestoreClient.class.getName();
75+
76+
private static class FirestoreClientService extends FirebaseService<FirestoreClient> {
77+
78+
FirestoreClientService(FirebaseApp app) {
79+
super(SERVICE_ID, new FirestoreClient(app));
80+
}
81+
82+
@Override
83+
public void destroy() {
84+
// NOTE: We don't explicitly tear down anything here (for now). User won't be able to call
85+
// FirestoreClient.getFirestore() any more, but already created Firestore instances will
86+
// continue to work. Request Firestore team to provide a cleanup/teardown method on the
87+
// Firestore object.
88+
}
89+
}
90+
91+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.google.firebase.cloud;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertFalse;
5+
import static org.junit.Assert.assertNotNull;
6+
7+
import com.google.cloud.firestore.DocumentReference;
8+
import com.google.cloud.firestore.Firestore;
9+
import com.google.cloud.firestore.WriteResult;
10+
import com.google.common.collect.ImmutableMap;
11+
import com.google.firebase.testing.IntegrationTestUtils;
12+
import java.util.Map;
13+
import org.junit.Test;
14+
15+
public class FirestoreClientIT {
16+
17+
@Test
18+
public void testFirestoreAccess() throws Exception {
19+
Firestore firestore = FirestoreClient.getFirestore(IntegrationTestUtils.ensureDefaultApp());
20+
DocumentReference reference = firestore.collection("cities").document("Mountain View");
21+
ImmutableMap<String, Object> expected = ImmutableMap.<String, Object>of(
22+
"name", "Mountain View",
23+
"country", "USA",
24+
"population", 77846L,
25+
"capital", false
26+
);
27+
WriteResult result = reference.set(expected).get();
28+
assertNotNull(result);
29+
30+
Map<String, Object> data = reference.get().get().getData();
31+
assertEquals(expected.size(), data.size());
32+
for (Map.Entry<String, Object> entry : expected.entrySet()) {
33+
assertEquals(entry.getValue(), data.get(entry.getKey()));
34+
}
35+
36+
reference.delete().get();
37+
assertFalse(reference.get().get().exists());
38+
}
39+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.google.firebase.cloud;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNotNull;
5+
import static org.junit.Assert.fail;
6+
7+
import com.google.auth.oauth2.GoogleCredentials;
8+
import com.google.cloud.firestore.Firestore;
9+
import com.google.firebase.FirebaseApp;
10+
import com.google.firebase.FirebaseOptions;
11+
import com.google.firebase.TestOnlyImplFirebaseTrampolines;
12+
import com.google.firebase.testing.ServiceAccount;
13+
import java.io.IOException;
14+
import org.junit.After;
15+
import org.junit.Test;
16+
17+
public class FirestoreClientTest {
18+
19+
@After
20+
public void tearDown() {
21+
TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
22+
}
23+
24+
@Test
25+
public void testExplicitProjectId() throws IOException {
26+
FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
27+
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
28+
.setProjectId("explicit-project-id")
29+
.build());
30+
Firestore firestore = FirestoreClient.getFirestore(app);
31+
assertEquals("explicit-project-id", firestore.getOptions().getProjectId());
32+
33+
firestore = FirestoreClient.getFirestore();
34+
assertEquals("explicit-project-id", firestore.getOptions().getProjectId());
35+
}
36+
37+
@Test
38+
public void testServiceAccountProjectId() throws IOException {
39+
FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
40+
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
41+
.build());
42+
Firestore firestore = FirestoreClient.getFirestore(app);
43+
assertEquals("mock-project-id", firestore.getOptions().getProjectId());
44+
45+
firestore = FirestoreClient.getFirestore();
46+
assertEquals("mock-project-id", firestore.getOptions().getProjectId());
47+
}
48+
49+
@Test
50+
public void testAppDelete() throws IOException {
51+
FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
52+
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
53+
.setProjectId("mock-project-id")
54+
.build());
55+
56+
assertNotNull(FirestoreClient.getFirestore(app));
57+
app.delete();
58+
try {
59+
FirestoreClient.getFirestore(app);
60+
fail("No error thrown for deleted app");
61+
} catch (IllegalStateException expected) {
62+
// ignore
63+
}
64+
65+
try {
66+
FirestoreClient.getFirestore();
67+
fail("No error thrown for deleted app");
68+
} catch (IllegalStateException expected) {
69+
// ignore
70+
}
71+
}
72+
73+
}

0 commit comments

Comments
 (0)