diff --git a/pom.xml b/pom.xml index 13b51555c..b82e5fc95 100644 --- a/pom.xml +++ b/pom.xml @@ -375,7 +375,7 @@ com.google.api-client google-api-client - 1.23.0 + 1.25.0 com.google.guava @@ -386,32 +386,32 @@ com.google.api-client google-api-client-gson - 1.23.0 + 1.25.0 com.google.http-client google-http-client - 1.23.0 + 1.25.0 com.google.api api-common - 1.2.0 + 1.7.0 com.google.auth google-auth-library-oauth2-http - 0.8.0 + 0.11.0 com.google.cloud google-cloud-storage - 1.27.0 + 1.43.0 com.google.cloud google-cloud-firestore - 0.45.0-beta + 0.61.0-beta diff --git a/src/main/java/com/google/firebase/FirebaseOptions.java b/src/main/java/com/google/firebase/FirebaseOptions.java index ae9b23370..99b4e1c5a 100644 --- a/src/main/java/com/google/firebase/FirebaseOptions.java +++ b/src/main/java/com/google/firebase/FirebaseOptions.java @@ -24,6 +24,7 @@ import com.google.api.client.json.JsonFactory; import com.google.api.client.util.Key; import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.firestore.FirestoreOptions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.firebase.internal.FirebaseThreadManagers; @@ -66,6 +67,7 @@ public final class FirebaseOptions { private final int readTimeout; private final JsonFactory jsonFactory; private final ThreadManager threadManager; + private final FirestoreOptions firestoreOptions; private FirebaseOptions(@NonNull FirebaseOptions.Builder builder) { this.credentials = checkNotNull(builder.credentials, @@ -94,6 +96,7 @@ private FirebaseOptions(@NonNull FirebaseOptions.Builder builder) { this.connectTimeout = builder.connectTimeout; checkArgument(builder.readTimeout >= 0); this.readTimeout = builder.readTimeout; + this.firestoreOptions = builder.firestoreOptions; } /** @@ -193,6 +196,19 @@ ThreadManager getThreadManager() { return threadManager; } + FirestoreOptions getFirestoreOptions() { + return firestoreOptions; + } + + /** + * Creates an empty builder. + * + * @return A new builder instance. + */ + public static Builder builder() { + return new Builder(); + } + /** * Builder for constructing {@link FirebaseOptions}. */ @@ -213,6 +229,7 @@ public static final class Builder { private String serviceAccountId; private GoogleCredentials credentials; + private FirestoreOptions firestoreOptions; private HttpTransport httpTransport = Utils.getDefaultTransport(); private JsonFactory jsonFactory = Utils.getDefaultJsonFactory(); private ThreadManager threadManager = FirebaseThreadManagers.DEFAULT_THREAD_MANAGER; @@ -239,6 +256,7 @@ public Builder(FirebaseOptions options) { threadManager = options.threadManager; connectTimeout = options.connectTimeout; readTimeout = options.readTimeout; + firestoreOptions = options.firestoreOptions; } /** @@ -384,6 +402,22 @@ public Builder setThreadManager(ThreadManager threadManager) { return this; } + /** + * Sets the FirestoreOptions used to initialize Firestore in the + * {@link com.google.firebase.cloud.FirestoreClient} API. This can be used to customize + * low-level transport (GRPC) parameters, and timestamp handling behavior. + * + *

If credentials or a project ID is set in FirestoreOptions, they will get + * overwritten by the corresponding parameters in FirebaseOptions. + * + * @param firestoreOptions A FirestoreOptions instance. + * @return This Builder instance is returned so subsequent calls can be chained. + */ + public Builder setFirestoreOptions(FirestoreOptions firestoreOptions) { + this.firestoreOptions = firestoreOptions; + return this; + } + /** * Sets the connect timeout for outgoing HTTP (REST) connections made by the SDK. This is used * when opening a communication link to a remote HTTP endpoint. This setting does not diff --git a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java index 5c173ae28..4b56e838c 100644 --- a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java +++ b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java @@ -18,6 +18,7 @@ import com.google.api.core.ApiFuture; import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.firestore.FirestoreOptions; import com.google.firebase.internal.FirebaseService; import com.google.firebase.internal.NonNull; @@ -43,6 +44,10 @@ public static String getProjectId(@NonNull FirebaseApp app) { return app.getProjectId(); } + public static FirestoreOptions getFirestoreOptions(@NonNull FirebaseApp app) { + return app.getOptions().getFirestoreOptions(); + } + public static boolean isDefaultApp(@NonNull FirebaseApp app) { return app.isDefaultApp(); } diff --git a/src/main/java/com/google/firebase/cloud/FirestoreClient.java b/src/main/java/com/google/firebase/cloud/FirestoreClient.java index 210124972..15171d14b 100644 --- a/src/main/java/com/google/firebase/cloud/FirestoreClient.java +++ b/src/main/java/com/google/firebase/cloud/FirestoreClient.java @@ -3,6 +3,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.api.gax.core.FixedCredentialsProvider; import com.google.cloud.firestore.Firestore; import com.google.cloud.firestore.FirestoreOptions; import com.google.common.base.Strings; @@ -34,8 +35,13 @@ private FirestoreClient(FirebaseApp app) { "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 GOOGLE_CLOUD_PROJECT environment variable."); - this.firestore = FirestoreOptions.newBuilder() - .setCredentials(ImplFirebaseTrampolines.getCredentials(app)) + FirestoreOptions userOptions = ImplFirebaseTrampolines.getFirestoreOptions(app); + FirestoreOptions.Builder builder = userOptions != null + ? userOptions.toBuilder() : FirestoreOptions.newBuilder(); + this.firestore = builder + // CredentialsProvider has highest priority in FirestoreOptions, so we set that. + .setCredentialsProvider( + FixedCredentialsProvider.create(ImplFirebaseTrampolines.getCredentials(app))) .setProjectId(projectId) .build() .getService(); diff --git a/src/test/java/com/google/firebase/FirebaseOptionsTest.java b/src/test/java/com/google/firebase/FirebaseOptionsTest.java index 72a9c4784..0623a3810 100644 --- a/src/test/java/com/google/firebase/FirebaseOptionsTest.java +++ b/src/test/java/com/google/firebase/FirebaseOptionsTest.java @@ -30,6 +30,7 @@ import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; +import com.google.cloud.firestore.FirestoreOptions; import com.google.firebase.testing.ServiceAccount; import com.google.firebase.testing.TestUtils; import java.io.IOException; @@ -75,6 +76,9 @@ protected ThreadFactory getThreadFactory() { public void createOptionsWithAllValuesSet() throws IOException { GsonFactory jsonFactory = new GsonFactory(); NetHttpTransport httpTransport = new NetHttpTransport(); + FirestoreOptions firestoreOptions = FirestoreOptions.newBuilder() + .setTimestampsInSnapshotsEnabled(true) + .build(); FirebaseOptions firebaseOptions = new FirebaseOptions.Builder() .setDatabaseUrl(FIREBASE_DB_URL) @@ -86,6 +90,7 @@ public void createOptionsWithAllValuesSet() throws IOException { .setThreadManager(MOCK_THREAD_MANAGER) .setConnectTimeout(30000) .setReadTimeout(60000) + .setFirestoreOptions(firestoreOptions) .build(); assertEquals(FIREBASE_DB_URL, firebaseOptions.getDatabaseUrl()); assertEquals(FIREBASE_STORAGE_BUCKET, firebaseOptions.getStorageBucket()); @@ -95,6 +100,7 @@ public void createOptionsWithAllValuesSet() throws IOException { assertSame(MOCK_THREAD_MANAGER, firebaseOptions.getThreadManager()); assertEquals(30000, firebaseOptions.getConnectTimeout()); assertEquals(60000, firebaseOptions.getReadTimeout()); + assertSame(firestoreOptions, firebaseOptions.getFirestoreOptions()); GoogleCredentials credentials = firebaseOptions.getCredentials(); assertNotNull(credentials); @@ -124,6 +130,7 @@ public void createOptionsWithOnlyMandatoryValuesSet() throws IOException { assertEquals( GoogleCredential.fromStream(ServiceAccount.EDITOR.asStream()).getServiceAccountId(), ((ServiceAccountCredentials) credentials).getClientEmail()); + assertNull(firebaseOptions.getFirestoreOptions()); } @Test @@ -185,6 +192,8 @@ public void checkToBuilderCreatesNewEquivalentInstance() { assertEquals(ALL_VALUES_OPTIONS.getThreadManager(), allValuesOptionsCopy.getThreadManager()); assertEquals(ALL_VALUES_OPTIONS.getConnectTimeout(), allValuesOptionsCopy.getConnectTimeout()); assertEquals(ALL_VALUES_OPTIONS.getReadTimeout(), allValuesOptionsCopy.getReadTimeout()); + assertSame(ALL_VALUES_OPTIONS.getFirestoreOptions(), + allValuesOptionsCopy.getFirestoreOptions()); } @Test(expected = IllegalArgumentException.class) diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java index 1a4ccd4af..db8a03047 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java @@ -36,6 +36,7 @@ import com.google.auth.oauth2.UserCredentials; import com.google.common.base.Defaults; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.ImplFirebaseTrampolines; @@ -132,7 +133,8 @@ public HttpTransport create() { } private static GoogleCredentials createCertificateCredential() throws IOException { - final MockTokenServerTransport transport = new MockTokenServerTransport(); + final MockTokenServerTransport transport = new MockTokenServerTransport( + "https://accounts.google.com/o/oauth2/token"); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); return ServiceAccountCredentials.fromStream(ServiceAccount.EDITOR.asStream(), new HttpTransportFactory() { diff --git a/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java b/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java index 737eab1fe..f7c88a5cf 100644 --- a/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java +++ b/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java @@ -2,12 +2,17 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.firestore.Firestore; +import com.google.cloud.firestore.FirestoreOptions; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; +import com.google.firebase.FirebaseOptions.Builder; +import com.google.firebase.ImplFirebaseTrampolines; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.testing.ServiceAccount; import java.io.IOException; @@ -26,6 +31,9 @@ public void testExplicitProjectId() throws IOException { FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) .setProjectId("explicit-project-id") + .setFirestoreOptions(FirestoreOptions.newBuilder() + .setTimestampsInSnapshotsEnabled(true) + .build()) .build()); Firestore firestore = FirestoreClient.getFirestore(app); assertEquals("explicit-project-id", firestore.getOptions().getProjectId()); @@ -38,6 +46,9 @@ public void testExplicitProjectId() throws IOException { public void testServiceAccountProjectId() throws IOException { FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) + .setFirestoreOptions(FirestoreOptions.newBuilder() + .setTimestampsInSnapshotsEnabled(true) + .build()) .build()); Firestore firestore = FirestoreClient.getFirestore(app); assertEquals("mock-project-id", firestore.getOptions().getProjectId()); @@ -46,11 +57,56 @@ public void testServiceAccountProjectId() throws IOException { assertEquals("mock-project-id", firestore.getOptions().getProjectId()); } + @Test + public void testFirestoreOptions() throws IOException { + FirebaseApp app = FirebaseApp.initializeApp(new Builder() + .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) + .setProjectId("explicit-project-id") + .setFirestoreOptions(FirestoreOptions.newBuilder() + .setTimestampsInSnapshotsEnabled(true) + .build()) + .build()); + Firestore firestore = FirestoreClient.getFirestore(app); + assertEquals("explicit-project-id", firestore.getOptions().getProjectId()); + assertTrue(firestore.getOptions().areTimestampsInSnapshotsEnabled()); + + firestore = FirestoreClient.getFirestore(); + assertEquals("explicit-project-id", firestore.getOptions().getProjectId()); + assertTrue(firestore.getOptions().areTimestampsInSnapshotsEnabled()); + } + + @Test + public void testFirestoreOptionsOverride() throws IOException { + FirebaseApp app = FirebaseApp.initializeApp(new Builder() + .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) + .setProjectId("explicit-project-id") + .setFirestoreOptions(FirestoreOptions.newBuilder() + .setTimestampsInSnapshotsEnabled(true) + .setProjectId("other-project-id") + .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) + .build()) + .build()); + Firestore firestore = FirestoreClient.getFirestore(app); + assertEquals("explicit-project-id", firestore.getOptions().getProjectId()); + assertTrue(firestore.getOptions().areTimestampsInSnapshotsEnabled()); + assertSame(ImplFirebaseTrampolines.getCredentials(app), + firestore.getOptions().getCredentialsProvider().getCredentials()); + + firestore = FirestoreClient.getFirestore(); + assertEquals("explicit-project-id", firestore.getOptions().getProjectId()); + assertTrue(firestore.getOptions().areTimestampsInSnapshotsEnabled()); + assertSame(ImplFirebaseTrampolines.getCredentials(app), + firestore.getOptions().getCredentialsProvider().getCredentials()); + } + @Test public void testAppDelete() throws IOException { FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) .setProjectId("mock-project-id") + .setFirestoreOptions(FirestoreOptions.newBuilder() + .setTimestampsInSnapshotsEnabled(true) + .build()) .build()); assertNotNull(FirestoreClient.getFirestore(app)); diff --git a/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java b/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java index 00466821f..7d2b9d4ea 100644 --- a/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java +++ b/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java @@ -20,10 +20,12 @@ import com.google.api.client.googleapis.util.Utils; import com.google.api.client.json.GenericJson; +import com.google.cloud.firestore.FirestoreOptions; import com.google.common.collect.ImmutableList; import com.google.common.io.CharStreams; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; +import com.google.firebase.FirebaseOptions.Builder; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; @@ -105,10 +107,13 @@ public static synchronized String getApiKey() { public static synchronized FirebaseApp ensureDefaultApp() { if (masterApp == null) { FirebaseOptions options = - new FirebaseOptions.Builder() + FirebaseOptions.builder() .setDatabaseUrl(getDatabaseUrl()) .setStorageBucket(getStorageBucket()) .setCredentials(TestUtils.getCertCredential(getServiceAccountCertificate())) + .setFirestoreOptions(FirestoreOptions.newBuilder() + .setTimestampsInSnapshotsEnabled(true) + .build()) .build(); masterApp = FirebaseApp.initializeApp(options); } diff --git a/src/test/java/com/google/firebase/testing/TestUtils.java b/src/test/java/com/google/firebase/testing/TestUtils.java index b889bc263..0dec4db3f 100644 --- a/src/test/java/com/google/firebase/testing/TestUtils.java +++ b/src/test/java/com/google/firebase/testing/TestUtils.java @@ -106,7 +106,8 @@ public static synchronized GoogleCredentials getApplicationDefaultCredentials() if (defaultCredentials != null) { return defaultCredentials; } - final MockTokenServerTransport transport = new MockTokenServerTransport(); + final MockTokenServerTransport transport = new MockTokenServerTransport( + "https://accounts.google.com/o/oauth2/token"); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), TEST_ADC_ACCESS_TOKEN); File serviceAccount = new File("src/test/resources/service_accounts", "editor.json"); Map environmentVariables =