Skip to content

Commit 2846f47

Browse files
committed
Implement MultiDB Support
1 parent eed0b78 commit 2846f47

File tree

3 files changed

+154
-59
lines changed

3 files changed

+154
-59
lines changed

Diff for: pom.xml

+8
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,14 @@
360360
<autoReleaseAfterClose>true</autoReleaseAfterClose>
361361
</configuration>
362362
</plugin>
363+
<plugin>
364+
<groupId>org.apache.maven.plugins</groupId>
365+
<artifactId>maven-compiler-plugin</artifactId>
366+
<configuration>
367+
<source>8</source>
368+
<target>8</target>
369+
</configuration>
370+
</plugin>
363371
</plugins>
364372
</build>
365373

Diff for: src/main/java/com/google/firebase/cloud/FirestoreClient.java

+71-12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
import org.slf4j.Logger;
1515
import org.slf4j.LoggerFactory;
1616

17+
import java.util.Collections;
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
1721
/**
1822
* {@code FirestoreClient} provides access to Google Cloud Firestore. Use this API to obtain a
1923
* <a href="https://googlecloudplatform.github.io/google-cloud-java/google-cloud-clients/apidocs/com/google/cloud/firestore/Firestore.html">{@code Firestore}</a>
@@ -32,7 +36,7 @@ public class FirestoreClient {
3236

3337
private final Firestore firestore;
3438

35-
private FirestoreClient(FirebaseApp app) {
39+
private FirestoreClient(FirebaseApp app, String databaseId) {
3640
checkNotNull(app, "FirebaseApp must not be null");
3741
String projectId = ImplFirebaseTrampolines.getProjectId(app);
3842
checkArgument(!Strings.isNullOrEmpty(projectId),
@@ -47,12 +51,13 @@ private FirestoreClient(FirebaseApp app) {
4751
.setCredentialsProvider(
4852
FixedCredentialsProvider.create(ImplFirebaseTrampolines.getCredentials(app)))
4953
.setProjectId(projectId)
54+
.setDatabaseId(databaseId)
5055
.build()
5156
.getService();
5257
}
5358

5459
/**
55-
* Returns the Firestore instance associated with the default Firebase app. Returns the same
60+
* Returns the default Firestore instance associated with the default Firebase app. Returns the same
5661
* instance for all invocations. The Firestore instance and all references obtained from it
5762
* becomes unusable, once the default app is deleted.
5863
*
@@ -65,7 +70,7 @@ public static Firestore getFirestore() {
6570
}
6671

6772
/**
68-
* Returns the Firestore instance associated with the specified Firebase app. For a given app,
73+
* Returns the default Firestore instance associated with the specified Firebase app. For a given app,
6974
* always returns the same instance. The Firestore instance and all references obtained from it
7075
* becomes unusable, once the specified app is deleted.
7176
*
@@ -75,32 +80,86 @@ public static Firestore getFirestore() {
7580
*/
7681
@NonNull
7782
public static Firestore getFirestore(FirebaseApp app) {
78-
return getInstance(app).firestore;
83+
return getFirestore(app, ImplFirebaseTrampolines.getFirestoreOptions(app).getDatabaseId());
84+
}
85+
86+
/**
87+
* Returns the Firestore instance associated with the specified Firebase app. For a given app,
88+
* always returns the same instance. The Firestore instance and all references obtained from it
89+
* becomes unusable, once the specified app is deleted.
90+
*
91+
* @param app A non-null {@link FirebaseApp}.
92+
* @param database - The name of database.
93+
* @return A non-null <a href="https://googlecloudplatform.github.io/google-cloud-java/google-cloud-clients/apidocs/com/google/cloud/firestore/Firestore.html">{@code Firestore}</a>
94+
* instance.
95+
*/
96+
@NonNull
97+
public static Firestore getFirestore(FirebaseApp app, String database) {
98+
return getInstance(app, database).firestore;
99+
}
100+
101+
/**
102+
* Returns the Firestore instance associated with the default Firebase app. Returns the same
103+
* instance for all invocations. The Firestore instance and all references obtained from it
104+
* becomes unusable, once the default app is deleted.
105+
*
106+
* @param database - The name of database.
107+
* @return A non-null <a href="https://googlecloudplatform.github.io/google-cloud-java/google-cloud-clients/apidocs/com/google/cloud/firestore/Firestore.html">{@code Firestore}</a>
108+
* instance.
109+
*/
110+
@NonNull
111+
public static Firestore getFirestore(String database) {
112+
return getFirestore(FirebaseApp.getInstance(), database);
79113
}
80114

81-
private static synchronized FirestoreClient getInstance(FirebaseApp app) {
115+
private static synchronized FirestoreClient getInstance(FirebaseApp app, String database) {
82116
FirestoreClientService service = ImplFirebaseTrampolines.getService(app,
83117
SERVICE_ID, FirestoreClientService.class);
84118
if (service == null) {
85119
service = ImplFirebaseTrampolines.addService(app, new FirestoreClientService(app));
86120
}
87-
return service.getInstance();
121+
return service.getInstance().get(database);
88122
}
89123

90124
private static final String SERVICE_ID = FirestoreClient.class.getName();
91125

92-
private static class FirestoreClientService extends FirebaseService<FirestoreClient> {
126+
private static class FirestoreClientService extends FirebaseService<FirestoreInstances> {
93127

94128
FirestoreClientService(FirebaseApp app) {
95-
super(SERVICE_ID, new FirestoreClient(app));
129+
super(SERVICE_ID, new FirestoreInstances(app));
96130
}
97131

98132
@Override
99133
public void destroy() {
100-
try {
101-
instance.firestore.close();
102-
} catch (Exception e) {
103-
logger.warn("Error while closing the Firestore instance", e);
134+
instance.destroy();
135+
}
136+
}
137+
138+
private static class FirestoreInstances {
139+
140+
private final FirebaseApp app;
141+
142+
private final Map<String, FirestoreClient> clients =
143+
Collections.synchronizedMap(new HashMap<>());
144+
145+
private FirestoreInstances(FirebaseApp app) {
146+
this.app = app;
147+
}
148+
149+
FirestoreClient get(String databaseId) {
150+
return clients.computeIfAbsent(databaseId, id -> new FirestoreClient(app, id));
151+
}
152+
153+
void destroy() {
154+
synchronized (clients) {
155+
for (FirestoreClient client : clients.values()) {
156+
try {
157+
client.firestore.close();
158+
} catch (Exception e) {
159+
logger.warn("Error while closing the Firestore instance", e);
160+
}
161+
}
162+
clients.clear();
104163
}
105164
}
106165
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
package com.google.firebase.cloud;
22

3-
import static org.junit.Assert.assertEquals;
4-
import static org.junit.Assert.assertNotNull;
5-
import static org.junit.Assert.assertSame;
6-
import static org.junit.Assert.assertTrue;
7-
import static org.junit.Assert.fail;
8-
93
import com.google.auth.oauth2.GoogleCredentials;
104
import com.google.cloud.firestore.DocumentReference;
115
import com.google.cloud.firestore.Firestore;
@@ -20,12 +14,15 @@
2014
import org.junit.After;
2115
import org.junit.Test;
2216

17+
import static org.junit.Assert.*;
18+
2319
public class FirestoreClientTest {
2420

2521
private static final FirestoreOptions FIRESTORE_OPTIONS = FirestoreOptions.newBuilder()
2622
// Setting credentials is not required (they get overridden by Admin SDK), but without
2723
// this Firestore logs an ugly warning during tests.
2824
.setCredentials(new MockGoogleCredentials("test-token"))
25+
.setDatabaseId("differedDefaultDatabaseId")
2926
.build();
3027

3128
@After
@@ -35,47 +32,75 @@ public void tearDown() {
3532

3633
@Test
3734
public void testExplicitProjectId() throws IOException {
35+
String databaseId = "databaseIdInTestExplicitProjectId";
3836
FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
3937
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
4038
.setProjectId("explicit-project-id")
4139
.setFirestoreOptions(FIRESTORE_OPTIONS)
4240
.build());
43-
Firestore firestore = FirestoreClient.getFirestore(app);
44-
assertEquals("explicit-project-id", firestore.getOptions().getProjectId());
41+
Firestore firestore1 = FirestoreClient.getFirestore(app);
42+
assertEquals("explicit-project-id", firestore1.getOptions().getProjectId());
43+
assertEquals(FIRESTORE_OPTIONS.getDatabaseId(), firestore1.getOptions().getDatabaseId());
44+
45+
assertSame(firestore1, FirestoreClient.getFirestore());
46+
47+
Firestore firestore2 = FirestoreClient.getFirestore(app, databaseId);
48+
assertEquals("explicit-project-id", firestore2.getOptions().getProjectId());
49+
assertEquals(databaseId, firestore2.getOptions().getDatabaseId());
50+
51+
assertSame(firestore2, FirestoreClient.getFirestore(databaseId));
4552

46-
firestore = FirestoreClient.getFirestore();
47-
assertEquals("explicit-project-id", firestore.getOptions().getProjectId());
53+
assertNotSame(firestore1, firestore2);
4854
}
4955

5056
@Test
5157
public void testServiceAccountProjectId() throws IOException {
58+
String databaseId = "databaseIdInTestServiceAccountProjectId";
5259
FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
5360
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
5461
.setFirestoreOptions(FIRESTORE_OPTIONS)
5562
.build());
56-
Firestore firestore = FirestoreClient.getFirestore(app);
57-
assertEquals("mock-project-id", firestore.getOptions().getProjectId());
63+
Firestore firestore1 = FirestoreClient.getFirestore(app);
64+
assertEquals("mock-project-id", firestore1.getOptions().getProjectId());
65+
assertEquals(FIRESTORE_OPTIONS.getDatabaseId(), firestore1.getOptions().getDatabaseId());
5866

59-
firestore = FirestoreClient.getFirestore();
60-
assertEquals("mock-project-id", firestore.getOptions().getProjectId());
67+
assertSame(firestore1, FirestoreClient.getFirestore());
68+
69+
Firestore firestore2 = FirestoreClient.getFirestore(app, databaseId);
70+
assertEquals("mock-project-id", firestore2.getOptions().getProjectId());
71+
assertEquals(databaseId, firestore2.getOptions().getDatabaseId());
72+
73+
assertSame(firestore2, FirestoreClient.getFirestore(databaseId));
74+
75+
assertNotSame(firestore1, firestore2);
6176
}
6277

6378
@Test
6479
public void testFirestoreOptions() throws IOException {
80+
String databaseId = "databaseIdInTestFirestoreOptions";
6581
FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
6682
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
6783
.setProjectId("explicit-project-id")
6884
.setFirestoreOptions(FIRESTORE_OPTIONS)
6985
.build());
70-
Firestore firestore = FirestoreClient.getFirestore(app);
71-
assertEquals("explicit-project-id", firestore.getOptions().getProjectId());
86+
Firestore firestore1 = FirestoreClient.getFirestore(app);
87+
assertEquals("explicit-project-id", firestore1.getOptions().getProjectId());
88+
assertEquals(FIRESTORE_OPTIONS.getDatabaseId(), firestore1.getOptions().getDatabaseId());
89+
90+
assertSame(firestore1, FirestoreClient.getFirestore());
7291

73-
firestore = FirestoreClient.getFirestore();
74-
assertEquals("explicit-project-id", firestore.getOptions().getProjectId());
92+
Firestore firestore2 = FirestoreClient.getFirestore(app, databaseId);
93+
assertEquals("explicit-project-id", firestore2.getOptions().getProjectId());
94+
assertEquals(databaseId, firestore2.getOptions().getDatabaseId());
95+
96+
assertSame(firestore2, FirestoreClient.getFirestore(databaseId));
97+
98+
assertNotSame(firestore1, firestore2);
7599
}
76100

77101
@Test
78102
public void testFirestoreOptionsOverride() throws IOException {
103+
String databaseId = "databaseIdInTestFirestoreOptions";
79104
FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
80105
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
81106
.setProjectId("explicit-project-id")
@@ -84,48 +109,51 @@ public void testFirestoreOptionsOverride() throws IOException {
84109
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
85110
.build())
86111
.build());
87-
Firestore firestore = FirestoreClient.getFirestore(app);
88-
assertEquals("explicit-project-id", firestore.getOptions().getProjectId());
112+
Firestore firestore1 = FirestoreClient.getFirestore(app);
113+
assertEquals("explicit-project-id", firestore1.getOptions().getProjectId());
89114
assertSame(ImplFirebaseTrampolines.getCredentials(app),
90-
firestore.getOptions().getCredentialsProvider().getCredentials());
115+
firestore1.getOptions().getCredentialsProvider().getCredentials());
116+
assertEquals("(default)", firestore1.getOptions().getDatabaseId());
117+
118+
assertSame(firestore1, FirestoreClient.getFirestore());
91119

92-
firestore = FirestoreClient.getFirestore();
93-
assertEquals("explicit-project-id", firestore.getOptions().getProjectId());
120+
Firestore firestore2 = FirestoreClient.getFirestore(app, databaseId);
121+
assertEquals("explicit-project-id", firestore2.getOptions().getProjectId());
94122
assertSame(ImplFirebaseTrampolines.getCredentials(app),
95-
firestore.getOptions().getCredentialsProvider().getCredentials());
123+
firestore2.getOptions().getCredentialsProvider().getCredentials());
124+
assertEquals(databaseId, firestore2.getOptions().getDatabaseId());
125+
126+
assertSame(firestore2, FirestoreClient.getFirestore(databaseId));
127+
128+
assertNotSame(firestore1, firestore2);
96129
}
97130

98131
@Test
99132
public void testAppDelete() throws IOException {
133+
String databaseId = "databaseIdInTestAppDelete";
100134
FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
101135
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
102136
.setProjectId("mock-project-id")
103137
.setFirestoreOptions(FIRESTORE_OPTIONS)
104138
.build());
105139

106-
Firestore firestore = FirestoreClient.getFirestore(app);
107-
assertNotNull(firestore);
108-
DocumentReference document = firestore.collection("collection").document("doc");
140+
Firestore firestore1 = FirestoreClient.getFirestore(app);
141+
assertNotNull(firestore1);
142+
assertSame(firestore1, FirestoreClient.getFirestore());
143+
144+
Firestore firestore2 = FirestoreClient.getFirestore(app, databaseId);
145+
assertNotNull(firestore2);
146+
assertSame(firestore2, FirestoreClient.getFirestore(databaseId));
147+
148+
assertNotSame(firestore1, firestore2);
149+
150+
DocumentReference document = firestore1.collection("collection").document("doc");
109151
app.delete();
110-
try {
111-
FirestoreClient.getFirestore(app);
112-
fail("No error thrown for deleted app");
113-
} catch (IllegalStateException expected) {
114-
// ignore
115-
}
116-
117-
try {
118-
document.get();
119-
fail("No error thrown for deleted app");
120-
} catch (IllegalStateException expected) {
121-
// ignore
122-
}
123-
124-
try {
125-
FirestoreClient.getFirestore();
126-
fail("No error thrown for deleted app");
127-
} catch (IllegalStateException expected) {
128-
// ignore
129-
}
152+
153+
assertThrows(IllegalStateException.class, () -> FirestoreClient.getFirestore(app));
154+
assertThrows(IllegalStateException.class, () -> document.get());
155+
assertThrows(IllegalStateException.class, () -> FirestoreClient.getFirestore());
156+
assertThrows(IllegalStateException.class, () -> FirestoreClient.getFirestore(app, databaseId));
157+
assertThrows(IllegalStateException.class, () -> FirestoreClient.getFirestore(databaseId));
130158
}
131159
}

0 commit comments

Comments
 (0)