diff --git a/CHANGELOG.md b/CHANGELOG.md index 9040ff20a..eba2e178f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Unreleased +- [added] `FirebaseAuth`, `FirebaseMessaging` and `FirebaseInstanceId` + interfaces now expose a set of blocking methods. Each operation has + blocking an asynchronous versions. +- [changed] Removed the deprecated `FirebaseCredential` interface. +- [changed] Removed the deprecated `Task` interface along with the + `com.google.firebase.tasks` package. +- [changed] Dropped support for App Engine's Java 7 runtime. Developers + are advised to use the Admin SDK with Java 8 when deploying to App + Engine. +- [changed] Removed the deprecated `FirebaseDatabase.setLogLevel()` API + and the related logging utilities. Developers should use SLF4J to + configure logging directly. # v5.11.0 diff --git a/src/main/java/com/google/firebase/FirebaseApp.java b/src/main/java/com/google/firebase/FirebaseApp.java index 52cffb15c..6c6710123 100644 --- a/src/main/java/com/google/firebase/FirebaseApp.java +++ b/src/main/java/com/google/firebase/FirebaseApp.java @@ -19,11 +19,11 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import static java.nio.charset.StandardCharsets.UTF_8; import com.google.api.client.googleapis.util.Utils; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonParser; +import com.google.api.core.ApiFuture; import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.OAuth2Credentials; @@ -34,18 +34,13 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; -import com.google.common.io.BaseEncoding; import com.google.firebase.internal.FirebaseAppStore; +import com.google.firebase.internal.FirebaseScheduledExecutor; import com.google.firebase.internal.FirebaseService; -import com.google.firebase.internal.GaeThreadFactory; +import com.google.firebase.internal.ListenableFuture2ApiFuture; import com.google.firebase.internal.NonNull; import com.google.firebase.internal.Nullable; -import com.google.firebase.internal.RevivingScheduledExecutor; -import com.google.firebase.tasks.Task; -import com.google.firebase.tasks.Tasks; - -import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; @@ -176,6 +171,9 @@ public static FirebaseApp getInstance(@NonNull String name) { * by looking up the {@code FIREBASE_CONFIG} environment variable. If the value of * the variable starts with '{', it is parsed as a JSON object. Otherwise it is * treated as a file name and the JSON content is read from the corresponding file. + * + * @throws IllegalStateException if the default app has already been initialized. + * @throws IllegalArgumentException if an error occurs while loading options from the environment. */ public static FirebaseApp initializeApp() { return initializeApp(DEFAULT_APP_NAME); @@ -185,13 +183,23 @@ public static FirebaseApp initializeApp() { * Initializes a named {@link FirebaseApp} instance using Google Application Default Credentials. * Loads additional {@link FirebaseOptions} from the environment in the same way as the * {@link #initializeApp()} method. + * + * @throws IllegalStateException if an app with the same name has already been initialized. + * @throws IllegalArgumentException if an error occurs while loading options from the environment. */ public static FirebaseApp initializeApp(String name) { - return initializeApp(getOptionsFromEnvironment(), name); + try { + return initializeApp(getOptionsFromEnvironment(), name); + } catch (IOException e) { + throw new IllegalArgumentException( + "Failed to load settings from the system's environment variables", e); + } } /** * Initializes the default {@link FirebaseApp} instance using the given options. + * + * @throws IllegalStateException if the default app has already been initialized. */ public static FirebaseApp initializeApp(FirebaseOptions options) { return initializeApp(options, DEFAULT_APP_NAME); @@ -241,19 +249,6 @@ static void clearInstancesForTest() { } } - /** - * Returns persistence key. Exists to support getting {@link FirebaseApp} persistence key after - * the app has been deleted. - */ - static String getPersistenceKey(String name, FirebaseOptions options) { - return BaseEncoding.base64Url().omitPadding().encode(name.getBytes(UTF_8)); - } - - /** Use this key to store data per FirebaseApp. */ - String getPersistenceKey() { - return FirebaseApp.getPersistenceKey(getName(), getOptions()); - } - private static List getAllAppNames() { Set allAppNames = new HashSet<>(); synchronized (appsLock) { @@ -317,10 +312,7 @@ String getProjectId() { @Override public boolean equals(Object o) { - if (!(o instanceof FirebaseApp)) { - return false; - } - return name.equals(((FirebaseApp) o).getName()); + return o instanceof FirebaseApp && name.equals(((FirebaseApp) o).getName()); } @Override @@ -383,8 +375,8 @@ private ScheduledExecutorService ensureScheduledExecutorService() { synchronized (lock) { checkNotDeleted(); if (scheduledExecutor == null) { - scheduledExecutor = new RevivingScheduledExecutor(threadManager.getThreadFactory(), - "firebase-scheduled-worker", GaeThreadFactory.isAvailable()); + scheduledExecutor = new FirebaseScheduledExecutor(getThreadFactory(), + "firebase-scheduled-worker"); } } } @@ -395,10 +387,9 @@ ThreadFactory getThreadFactory() { return threadManager.getThreadFactory(); } - // TODO: Return an ApiFuture once Task API is fully removed. - Task submit(Callable command) { + ApiFuture submit(Callable command) { checkNotNull(command); - return Tasks.call(executors.getListeningExecutor(), command); + return new ListenableFuture2ApiFuture<>(executors.getListeningExecutor().submit(command)); } ScheduledFuture schedule(Callable command, long delayMillis) { @@ -462,7 +453,7 @@ static class TokenRefresher implements CredentialsChangedListener { } @Override - public final synchronized void onChanged(OAuth2Credentials credentials) throws IOException { + public final synchronized void onChanged(OAuth2Credentials credentials) { if (state.get() != State.STARTED) { return; } @@ -569,33 +560,26 @@ enum State { } } - private static FirebaseOptions getOptionsFromEnvironment() { + private static FirebaseOptions getOptionsFromEnvironment() throws IOException { String defaultConfig = System.getenv(FIREBASE_CONFIG_ENV_VAR); - try { - if (Strings.isNullOrEmpty(defaultConfig)) { - return new FirebaseOptions.Builder() - .setCredentials(GoogleCredentials.getApplicationDefault()) - .build(); - } - JsonFactory jsonFactory = Utils.getDefaultJsonFactory(); - FirebaseOptions.Builder builder = new FirebaseOptions.Builder(); - JsonParser parser; - if (defaultConfig.startsWith("{")) { - parser = jsonFactory.createJsonParser(defaultConfig); - } else { - FileReader reader; - reader = new FileReader(defaultConfig); - parser = jsonFactory.createJsonParser(reader); - } - parser.parseAndClose(builder); - builder.setCredentials(GoogleCredentials.getApplicationDefault()); - - return builder.build(); + if (Strings.isNullOrEmpty(defaultConfig)) { + return new FirebaseOptions.Builder() + .setCredentials(GoogleCredentials.getApplicationDefault()) + .build(); + } - } catch (FileNotFoundException e) { - throw new IllegalStateException(e); - } catch (IOException e) { - throw new IllegalStateException(e); + JsonFactory jsonFactory = Utils.getDefaultJsonFactory(); + FirebaseOptions.Builder builder = new FirebaseOptions.Builder(); + JsonParser parser; + if (defaultConfig.startsWith("{")) { + parser = jsonFactory.createJsonParser(defaultConfig); + } else { + FileReader reader; + reader = new FileReader(defaultConfig); + parser = jsonFactory.createJsonParser(reader); } + parser.parseAndClose(builder); + builder.setCredentials(GoogleCredentials.getApplicationDefault()); + return builder.build(); } } diff --git a/src/main/java/com/google/firebase/FirebaseOptions.java b/src/main/java/com/google/firebase/FirebaseOptions.java index 28c9e9e66..0cdf87bf8 100644 --- a/src/main/java/com/google/firebase/FirebaseOptions.java +++ b/src/main/java/com/google/firebase/FirebaseOptions.java @@ -25,21 +25,35 @@ import com.google.api.client.util.Key; import com.google.auth.oauth2.GoogleCredentials; import com.google.common.base.Strings; -import com.google.firebase.auth.FirebaseCredential; -import com.google.firebase.auth.FirebaseCredentials; -import com.google.firebase.auth.internal.BaseCredential; -import com.google.firebase.auth.internal.FirebaseCredentialsAdapter; +import com.google.common.collect.ImmutableList; import com.google.firebase.internal.FirebaseThreadManagers; import com.google.firebase.internal.NonNull; import com.google.firebase.internal.Nullable; import java.util.HashMap; +import java.util.List; import java.util.Map; /** Configurable Firebase options. */ public final class FirebaseOptions { - // TODO: deprecate and remove it once we can fetch these from Remote Config. + private static final List FIREBASE_SCOPES = + ImmutableList.of( + // Enables access to Firebase Realtime Database. + "https://www.googleapis.com/auth/firebase.database", + + // Enables access to the email address associated with a project. + "https://www.googleapis.com/auth/userinfo.email", + + // Enables access to Google Identity Toolkit (for user management APIs). + "https://www.googleapis.com/auth/identitytoolkit", + + // Enables access to Google Cloud Storage. + "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 String databaseUrl; private final String storageBucket; @@ -55,7 +69,7 @@ public final class FirebaseOptions { private FirebaseOptions(@NonNull FirebaseOptions.Builder builder) { this.credentials = checkNotNull(builder.credentials, "FirebaseOptions must be initialized with setCredentials().") - .createScoped(BaseCredential.FIREBASE_SCOPES); + .createScoped(FIREBASE_SCOPES); this.databaseUrl = builder.databaseUrl; this.databaseAuthVariableOverride = builder.databaseAuthVariableOverride; this.projectId = builder.projectId; @@ -260,24 +274,6 @@ public Builder setCredentials(GoogleCredentials credentials) { return this; } - /** - * Sets the FirebaseCredential to use to authenticate the SDK. - * - * @param credential A FirebaseCredential used to authenticate the SDK. See {@link - * FirebaseCredentials} for default implementations. - * @return This Builder instance is returned so subsequent calls can be chained. - * @deprecated Use {@link FirebaseOptions.Builder#setCredentials(GoogleCredentials)}. - */ - public Builder setCredential(@NonNull FirebaseCredential credential) { - checkNotNull(credential); - if (credential instanceof BaseCredential) { - this.credentials = ((BaseCredential) credential).getGoogleCredentials(); - } else { - this.credentials = new FirebaseCredentialsAdapter(credential); - } - return this; - } - /** * Sets the auth variable to be used by the Realtime Database rules. * diff --git a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java index 7ffac6d11..5c173ae28 100644 --- a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java +++ b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java @@ -16,11 +16,11 @@ package com.google.firebase; +import com.google.api.core.ApiFuture; import com.google.auth.oauth2.GoogleCredentials; import com.google.firebase.internal.FirebaseService; import com.google.firebase.internal.NonNull; -import com.google.firebase.tasks.Task; import java.util.concurrent.Callable; import java.util.concurrent.ThreadFactory; @@ -47,14 +47,6 @@ public static boolean isDefaultApp(@NonNull FirebaseApp app) { return app.isDefaultApp(); } - public static String getPersistenceKey(@NonNull FirebaseApp app) { - return app.getPersistenceKey(); - } - - public static String getPersistenceKey(String name, FirebaseOptions options) { - return FirebaseApp.getPersistenceKey(name, options); - } - public static T getService( @NonNull FirebaseApp app, @NonNull String id, @NonNull Class type) { return type.cast(app.getService(id)); @@ -70,7 +62,8 @@ public static ThreadFactory getThreadFactory(@NonNull FirebaseApp app) { return app.getThreadFactory(); } - public static Task submitCallable(@NonNull FirebaseApp app, @NonNull Callable command) { + public static ApiFuture submitCallable( + @NonNull FirebaseApp app, @NonNull Callable command) { return app.submit(command); } diff --git a/src/main/java/com/google/firebase/ThreadManager.java b/src/main/java/com/google/firebase/ThreadManager.java index 4f3c5f29d..8903101f6 100644 --- a/src/main/java/com/google/firebase/ThreadManager.java +++ b/src/main/java/com/google/firebase/ThreadManager.java @@ -57,7 +57,7 @@ final void releaseFirebaseExecutors( * {@link #getThreadFactory()} method. * * @param app A {@link FirebaseApp} instance. - * @return A non-null {@link ExecutorService} instance. + * @return A non-null ExecutorService instance. */ @NonNull protected abstract ExecutorService getExecutor(@NonNull FirebaseApp app); diff --git a/src/main/java/com/google/firebase/auth/FirebaseAuth.java b/src/main/java/com/google/firebase/auth/FirebaseAuth.java index 3150b1d75..3181dc5b1 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/FirebaseAuth.java @@ -36,13 +36,13 @@ import com.google.firebase.auth.internal.FirebaseTokenFactory; import com.google.firebase.auth.internal.FirebaseTokenVerifier; import com.google.firebase.auth.internal.KeyManagers; +import com.google.firebase.internal.CallableOperation; import com.google.firebase.internal.FirebaseService; import com.google.firebase.internal.NonNull; import com.google.firebase.internal.Nullable; -import com.google.firebase.internal.TaskToApiFuture; -import com.google.firebase.tasks.Task; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.Map; -import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -55,6 +55,10 @@ */ public class FirebaseAuth { + private static final String ERROR_CUSTOM_TOKEN = "ERROR_CUSTOM_TOKEN"; + private static final String ERROR_INVALID_ID_TOKEN = "ERROR_INVALID_CREDENTIAL"; + private static final String ERROR_INVALID_SESSION_COOKIE = "ERROR_INVALID_COOKIE"; + private final Clock clock; private final FirebaseApp firebaseApp; @@ -111,25 +115,27 @@ public static synchronized FirebaseAuth getInstance(FirebaseApp app) { return service.getInstance(); } - private Task createSessionCookie( - final String idToken, final SessionCookieOptions options) { - checkNotDestroyed(); - checkArgument(!Strings.isNullOrEmpty(idToken), "idToken must not be null or empty"); - checkNotNull(options, "options must not be null"); - return call(new Callable() { - @Override - public String call() throws Exception { - return userManager.createSessionCookie(idToken, options); - } - }); - } - /** * Creates a new Firebase session cookie from the given ID token and options. The returned JWT * can be set as a server-side session cookie with a custom cookie policy. * * @param idToken The Firebase ID token to exchange for a session cookie. * @param options Additional options required to create the cookie. + * @return A Firebase session cookie string. + * @throws IllegalArgumentException If the ID token is null or empty, or if options is null. + * @throws FirebaseAuthException If an error occurs while generating the session cookie. + */ + public String createSessionCookie( + @NonNull String idToken, @NonNull SessionCookieOptions options) throws FirebaseAuthException { + return createSessionCookieOp(idToken, options).call(); + } + + /** + * Similar to {@link #createSessionCookie(String, SessionCookieOptions)} but performs the + * operation asynchronously. + * + * @param idToken The Firebase ID token to exchange for a session cookie. + * @param options Additional options required to create the cookie. * @return An {@code ApiFuture} which will complete successfully with a session cookie string. * If an error occurs while generating the cookie or if the specified ID token is invalid, * the future throws a {@link FirebaseAuthException}. @@ -137,25 +143,36 @@ public String call() throws Exception { */ public ApiFuture createSessionCookieAsync( @NonNull String idToken, @NonNull SessionCookieOptions options) { - return new TaskToApiFuture<>(createSessionCookie(idToken, options)); + return createSessionCookieOp(idToken, options).callAsync(firebaseApp); + } + + private CallableOperation createSessionCookieOp( + final String idToken, final SessionCookieOptions options) { + checkNotDestroyed(); + checkArgument(!Strings.isNullOrEmpty(idToken), "idToken must not be null or empty"); + checkNotNull(options, "options must not be null"); + return new CallableOperation() { + @Override + protected String execute() throws FirebaseAuthException { + return userManager.createSessionCookie(idToken, options); + } + }; } /** * Parses and verifies a Firebase session cookie. * - *

If verified successfully, the returned {@code ApiFuture} completes with a parsed version of - * the cookie from which the UID and the other claims can be read. If the cookie is invalid, - * the future throws an exception indicating the failure. + *

If verified successfully, returns a parsed version of the cookie from which the UID and the + * other claims can be read. If the cookie is invalid, throws a {@link FirebaseAuthException}. * *

This method does not check whether the cookie has been revoked. See - * {@link #verifySessionCookieAsync(String, boolean)}. + * {@link #verifySessionCookie(String, boolean)}. * * @param cookie A Firebase session cookie string to verify and parse. - * @return An {@code ApiFuture} which will complete successfully with the parsed cookie, or - * unsuccessfully with the failure Exception. + * @return A {@link FirebaseToken} representing the verified and decoded cookie. */ - public ApiFuture verifySessionCookieAsync(String cookie) { - return new TaskToApiFuture<>(verifySessionCookie(cookie, false)); + public FirebaseToken verifySessionCookie(String cookie) throws FirebaseAuthException { + return verifySessionCookie(cookie, false); } /** @@ -164,10 +181,34 @@ public ApiFuture verifySessionCookieAsync(String cookie) { *

If {@code checkRevoked} is true, additionally verifies that the cookie has not been * revoked. * - *

If verified successfully, the returned {@code ApiFuture} completes with a parsed version of - * the cookie from which the UID and the other claims can be read. If the cookie is invalid or - * has been revoked while {@code checkRevoked} is true, the future throws an exception indicating - * the failure. + *

If verified successfully, returns a parsed version of the cookie from which the UID and the + * other claims can be read. If the cookie is invalid or has been revoked while + * {@code checkRevoked} is true, throws a {@link FirebaseAuthException}. + * + * @param cookie A Firebase session cookie string to verify and parse. + * @param checkRevoked A boolean indicating whether to check if the cookie was explicitly + * revoked. + * @return A {@link FirebaseToken} representing the verified and decoded cookie. + */ + public FirebaseToken verifySessionCookie( + String cookie, boolean checkRevoked) throws FirebaseAuthException { + return verifySessionCookieOp(cookie, checkRevoked).call(); + } + + /** + * Similar to {@link #verifySessionCookie(String)} but performs the operation asynchronously. + * + * @param cookie A Firebase session cookie string to verify and parse. + * @return An {@code ApiFuture} which will complete successfully with the parsed cookie, or + * unsuccessfully with the failure Exception. + */ + public ApiFuture verifySessionCookieAsync(String cookie) { + return verifySessionCookieAsync(cookie, false); + } + + /** + * Similar to {@link #verifySessionCookie(String, boolean)} but performs the operation + * asynchronously. * * @param cookie A Firebase session cookie string to verify and parse. * @param checkRevoked A boolean indicating whether to check if the cookie was explicitly @@ -176,19 +217,26 @@ public ApiFuture verifySessionCookieAsync(String cookie) { * unsuccessfully with the failure Exception. */ public ApiFuture verifySessionCookieAsync(String cookie, boolean checkRevoked) { - return new TaskToApiFuture<>(verifySessionCookie(cookie, checkRevoked)); + return verifySessionCookieOp(cookie, checkRevoked).callAsync(firebaseApp); } - private Task verifySessionCookie(final String cookie, final boolean checkRevoked) { + private CallableOperation verifySessionCookieOp( + final String cookie, final boolean checkRevoked) { checkNotDestroyed(); checkState(!Strings.isNullOrEmpty(projectId), "Must initialize FirebaseApp with a project ID to call verifySessionCookie()"); - return call(new Callable() { + return new CallableOperation() { @Override - public FirebaseToken call() throws Exception { + public FirebaseToken execute() throws FirebaseAuthException { FirebaseTokenVerifier firebaseTokenVerifier = FirebaseTokenVerifier.createSessionCookieVerifier(projectId, keyManagers, clock); - FirebaseToken firebaseToken = FirebaseToken.parse(jsonFactory, cookie); + FirebaseToken firebaseToken; + try { + firebaseToken = FirebaseToken.parse(jsonFactory, cookie); + } catch (IOException e) { + throw new FirebaseAuthException(ERROR_INVALID_SESSION_COOKIE, + "Failed to parse cookie", e); + } // This will throw a FirebaseAuthException with details on how the token is invalid. firebaseTokenVerifier.verifyTokenAndSignature(firebaseToken.getToken()); @@ -198,7 +246,7 @@ public FirebaseToken call() throws Exception { } return firebaseToken; } - }); + }; } private void checkRevoked( @@ -212,106 +260,202 @@ private void checkRevoked( } /** - * Similar to {@link #createCustomTokenAsync(String)}, but returns a {@link Task}. + * Creates a Firebase custom token for the given UID. This token can then be sent back to a client + * application to be used with the + * signInWithCustomToken + * authentication API. + * + *

{@link FirebaseApp} must have been initialized with service account credentials to use + * call this method. * * @param uid The UID to store in the token. This identifies the user to other Firebase services - * (Firebase Database, Firebase Auth, etc.) - * @return A {@link Task} which will complete successfully with the created Firebase Custom Token, - * or unsuccessfully with the failure Exception. - * @deprecated Use {@link #createCustomTokenAsync(String)} + * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters. + * @return A Firebase custom token string. + * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not + * been initialized with service account credentials. + * @throws FirebaseAuthException If an error occurs while generating the custom token. */ - public Task createCustomToken(String uid) { + public String createCustomToken(@NonNull String uid) throws FirebaseAuthException { return createCustomToken(uid, null); } /** - * Similar to {@link #createCustomTokenAsync(String, Map)}, but returns a {@link Task}. + * Creates a Firebase custom token for the given UID, containing the specified additional + * claims. This token can then be sent back to a client application to be used with the + * signInWithCustomToken + * authentication API. + * + *

{@link FirebaseApp} must have been initialized with service account credentials to use + * call this method. * * @param uid The UID to store in the token. This identifies the user to other Firebase services - * (Realtime Database, Storage, etc.). Should be less than 128 characters. + * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters. * @param developerClaims Additional claims to be stored in the token (and made available to * security rules in Database, Storage, etc.). These must be able to be serialized to JSON * (e.g. contain only Maps, Arrays, Strings, Booleans, Numbers, etc.) - * @return A {@link Task} which will complete successfully with the created Firebase Custom Token, - * or unsuccessfully with the failure Exception. - * @deprecated Use {@link #createCustomTokenAsync(String, Map)} + * @return A Firebase custom token string. + * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not + * been initialized with service account credentials. + * @throws FirebaseAuthException If an error occurs while generating the custom token. */ - public Task createCustomToken( - final String uid, final Map developerClaims) { - checkNotDestroyed(); - checkState(credentials instanceof ServiceAccountCredentials, - "Must initialize FirebaseApp with a service account credential to call " - + "createCustomToken()"); - - final ServiceAccountCredentials serviceAccount = (ServiceAccountCredentials) credentials; - return call(new Callable() { - @Override - public String call() throws Exception { - FirebaseTokenFactory tokenFactory = FirebaseTokenFactory.getInstance(); - return tokenFactory.createSignedCustomAuthTokenForUser( - uid, - developerClaims, - serviceAccount.getClientEmail(), - serviceAccount.getPrivateKey()); - } - }); + public String createCustomToken(@NonNull String uid, + @Nullable Map developerClaims) throws FirebaseAuthException { + return createCustomTokenOp(uid, developerClaims).call(); } /** - * Creates a Firebase Custom Token associated with the given UID. This token can then be provided - * back to a client application for use with the - * signInWithCustomToken - * authentication API. + * Similar to {@link #createCustomToken(String)} but performs the operation asynchronously. * * @param uid The UID to store in the token. This identifies the user to other Firebase services - * (Firebase Realtime Database, Firebase Auth, etc.) - * @return An {@code ApiFuture} which will complete successfully with the created Firebase Custom - * Token, or unsuccessfully with the failure Exception. + * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters. + * @return An {@code ApiFuture} which will complete successfully with the created Firebase custom + * token, or unsuccessfully with the failure Exception. + * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not + * been initialized with service account credentials. */ - public ApiFuture createCustomTokenAsync(String uid) { - return new TaskToApiFuture<>(createCustomToken(uid)); + public ApiFuture createCustomTokenAsync(@NonNull String uid) { + return createCustomTokenAsync(uid, null); } /** - * Creates a Firebase Custom Token associated with the given UID and additionally containing the - * specified developerClaims. This token can then be provided back to a client application for use - * with the signInWithCustomToken authentication API. + * Similar to {@link #createCustomToken(String, Map)} but performs the operation + * asynchronously. * * @param uid The UID to store in the token. This identifies the user to other Firebase services * (Realtime Database, Storage, etc.). Should be less than 128 characters. * @param developerClaims Additional claims to be stored in the token (and made available to * security rules in Database, Storage, etc.). These must be able to be serialized to JSON * (e.g. contain only Maps, Arrays, Strings, Booleans, Numbers, etc.) - * @return An {@code ApiFuture} which will complete successfully with the created Firebase Custom - * Token, or unsuccessfully with the failure Exception. + * @return An {@code ApiFuture} which will complete successfully with the created Firebase custom + * token, or unsuccessfully with the failure Exception. + * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not + * been initialized with service account credentials. */ public ApiFuture createCustomTokenAsync( + @NonNull String uid, @Nullable Map developerClaims) { + return createCustomTokenOp(uid, developerClaims).callAsync(firebaseApp); + } + + private CallableOperation createCustomTokenOp( final String uid, final Map developerClaims) { - return new TaskToApiFuture<>(createCustomToken(uid, developerClaims)); + checkNotDestroyed(); + checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); + checkArgument(credentials instanceof ServiceAccountCredentials, + "Must initialize FirebaseApp with a service account credential to call " + + "createCustomToken()"); + return new CallableOperation() { + @Override + public String execute() throws FirebaseAuthException { + final ServiceAccountCredentials serviceAccount = (ServiceAccountCredentials) credentials; + FirebaseTokenFactory tokenFactory = FirebaseTokenFactory.getInstance(); + try { + return tokenFactory.createSignedCustomAuthTokenForUser( + uid, + developerClaims, + serviceAccount.getClientEmail(), + serviceAccount.getPrivateKey()); + } catch (GeneralSecurityException | IOException e) { + throw new FirebaseAuthException(ERROR_CUSTOM_TOKEN, + "Failed to generate a custom token", e); + } + } + }; } /** - * Similar to {@link #verifyIdTokenAsync(String)}, but returns a {@link Task}. + * Parses and verifies a Firebase ID Token. * - * @param token A Firebase ID Token to verify and parse. - * @return A {@link Task} which will complete successfully with the parsed token, or - * unsuccessfully with the failure Exception. - * @deprecated Use {@link #verifyIdTokenAsync(String)} + *

A Firebase application can identify itself to a trusted backend server by sending its + * Firebase ID Token (accessible via the {@code getToken} API in the Firebase Authentication + * client) with its requests. The backend server can then use the {@code verifyIdToken()} method + * to verify that the token is valid. This method ensures that the token is correctly signed, + * has not expired, and it was issued to the Firebase project associated with this + * {@link FirebaseAuth} instance. + * + *

This method does not check whether a token has been revoked. Use + * {@link #verifyIdToken(String, boolean)} to perform an additional revocation check. + * + * @param token A Firebase ID token string to parse and verify. + * @return A {@link FirebaseToken} representing the verified and decoded token. + * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp} + * instance does not have a project ID associated with it. + * @throws FirebaseAuthException If an error occurs while parsing or validating the token. */ - public Task verifyIdToken(final String token) { + public FirebaseToken verifyIdToken(@NonNull String token) throws FirebaseAuthException { return verifyIdToken(token, false); } - - private Task verifyIdToken(final String token, final boolean checkRevoked) { + + /** + * Parses and verifies a Firebase ID Token. + * + *

A Firebase application can identify itself to a trusted backend server by sending its + * Firebase ID Token (accessible via the {@code getToken} API in the Firebase Authentication + * client) with its requests. The backend server can then use the {@code verifyIdToken()} method + * to verify that the token is valid. This method ensures that the token is correctly signed, + * has not expired, and it was issued to the Firebase project associated with this + * {@link FirebaseAuth} instance. + * + *

If {@code checkRevoked} is set to true, this method performs an additional check to see + * if the ID token has been revoked since it was issues. This requires making an additional + * remote API call. + * + * @param token A Firebase ID token string to parse and verify. + * @param checkRevoked A boolean denoting whether to check if the tokens were revoked. + * @return A {@link FirebaseToken} representing the verified and decoded token. + * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp} + * instance does not have a project ID associated with it. + * @throws FirebaseAuthException If an error occurs while parsing or validating the token. + */ + public FirebaseToken verifyIdToken( + @NonNull String token, boolean checkRevoked) throws FirebaseAuthException { + return verifyIdTokenOp(token, checkRevoked).call(); + } + + /** + * Similar to {@link #verifyIdToken(String)} but performs the operation asynchronously. + * + * @param token A Firebase ID Token to verify and parse. + * @return An {@code ApiFuture} which will complete successfully with the parsed token, or + * unsuccessfully with a {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp} + * instance does not have a project ID associated with it. + */ + public ApiFuture verifyIdTokenAsync(@NonNull String token) { + return verifyIdTokenAsync(token, false); + } + + /** + * Similar to {@link #verifyIdToken(String, boolean)} but performs the operation asynchronously. + * + * @param token A Firebase ID Token to verify and parse. + * @param checkRevoked A boolean denoting whether to check if the tokens were revoked. + * @return An {@code ApiFuture} which will complete successfully with the parsed token, or + * unsuccessfully with a {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp} + * instance does not have a project ID associated with it. + */ + public ApiFuture verifyIdTokenAsync(@NonNull String token, boolean checkRevoked) { + return verifyIdTokenOp(token, checkRevoked).callAsync(firebaseApp); + } + + private CallableOperation verifyIdTokenOp( + final String token, final boolean checkRevoked) { checkNotDestroyed(); - checkState(!Strings.isNullOrEmpty(projectId), + checkArgument(!Strings.isNullOrEmpty(token), "ID token must not be null or empty"); + checkArgument(!Strings.isNullOrEmpty(projectId), "Must initialize FirebaseApp with a project ID to call verifyIdToken()"); - return call(new Callable() { + return new CallableOperation() { @Override - public FirebaseToken call() throws Exception { + protected FirebaseToken execute() throws FirebaseAuthException { FirebaseTokenVerifier firebaseTokenVerifier = FirebaseTokenVerifier.createIdTokenVerifier(projectId, keyManagers, clock); - FirebaseToken firebaseToken = FirebaseToken.parse(jsonFactory, token); + FirebaseToken firebaseToken; + try { + firebaseToken = FirebaseToken.parse(jsonFactory, token); + } catch (IOException e) { + throw new FirebaseAuthException(ERROR_INVALID_ID_TOKEN, "Failed to parse token", e); + } + // This will throw a FirebaseAuthException with details on how the token is invalid. firebaseTokenVerifier.verifyTokenAndSignature(firebaseToken.getToken()); @@ -320,122 +464,69 @@ public FirebaseToken call() throws Exception { } return firebaseToken; } - }); - } - - private Task revokeRefreshTokens(String uid) { - checkNotDestroyed(); - long currentTimeSeconds = System.currentTimeMillis() / 1000L; - final UpdateRequest request = new UpdateRequest(uid).setValidSince(currentTimeSeconds); - return call(new Callable() { - @Override - public Void call() throws Exception { - userManager.updateUser(request, jsonFactory); - return null; - } - }); + }; } /** * Revokes all refresh tokens for the specified user. - * + * *

Updates the user's tokensValidAfterTimestamp to the current UTC time expressed in * milliseconds since the epoch and truncated to 1 second accuracy. It is important that the * server on which this is called has its clock set correctly and synchronized. - * + * *

While this will revoke all sessions for a specified user and disable any new ID tokens for * existing sessions from getting minted, existing ID tokens may remain active until their - * natural expiration (one hour). + * natural expiration (one hour). * To verify that ID tokens are revoked, use {@link #verifyIdTokenAsync(String, boolean)}. - * + * * @param uid The user id for which tokens are revoked. - * @return An {@code ApiFuture} which will complete successfully or if updating the user fails, - * unsuccessfully with the failure Exception. + * @throws IllegalArgumentException If the user ID is null or empty. + * @throws FirebaseAuthException If an error occurs while revoking tokens. */ - public ApiFuture revokeRefreshTokensAsync(String uid) { - return new TaskToApiFuture<>(revokeRefreshTokens(uid)); + public void revokeRefreshTokens(@NonNull String uid) throws FirebaseAuthException { + revokeRefreshTokensOp(uid).call(); } /** - * Parses and verifies a Firebase ID Token. - * - *

A Firebase application can identify itself to a trusted backend server by sending its - * Firebase ID Token (accessible via the getToken API in the Firebase Authentication client) with - * its request. - * - *

The backend server can then use the verifyIdToken() method to verify the token is valid, - * meaning: the token is properly signed, has not expired, and it was issued for the project - * associated with this FirebaseAuth instance (which by default is extracted from your service - * account) - * - *

If the token is valid, the returned Future will complete successfully and provide a - * parsed version of the token from which the UID and other claims in the token can be inspected. - * If the token is invalid, the future throws an exception indicating the failure. + * Similar to {@link #revokeRefreshTokens(String)} but performs the operation asynchronously. * - *

This does not check whether a token has been revoked. - * See {@link #verifyIdTokenAsync(String, boolean)} below. - * - * @param token A Firebase ID Token to verify and parse. - * @return An {@code ApiFuture} which will complete successfully with the parsed token, or - * unsuccessfully with the failure Exception. + * @param uid The user id for which tokens are revoked. + * @return An {@code ApiFuture} which will complete successfully or fail with a + * {@link FirebaseAuthException} in the event of an error. + * @throws IllegalArgumentException If the user ID is null or empty. */ - public ApiFuture verifyIdTokenAsync(final String token) { - return verifyIdTokenAsync(token, false); + public ApiFuture revokeRefreshTokensAsync(@NonNull String uid) { + return revokeRefreshTokensOp(uid).callAsync(firebaseApp); } - /** - * Parses and verifies a Firebase ID Token and if requested, checks whether it was revoked. - * - *

A Firebase application can identify itself to a trusted backend server by sending its - * Firebase ID Token (accessible via the getToken API in the Firebase Authentication client) with - * its request. - * - *

The backend server can then use the verifyIdToken() method to verify the token is valid, - * meaning: the token is properly signed, has not expired, and it was issued for the project - * associated with this FirebaseAuth instance (which by default is extracted from your service - * account) - * - *

If {@code checkRevoked} is true, additionally checks if the token has been revoked. - * - *

If the token is valid, and not revoked, the returned Future will complete successfully and - * provide a parsed version of the token from which the UID and other claims in the token can be - * inspected. - * If the token is invalid or has been revoked, the future throws an exception indicating the - * failure. - * - * @param token A Firebase ID Token to verify and parse. - * @param checkRevoked A boolean indicating whether to check if the tokens were revoked. - * @return An {@code ApiFuture} which will complete successfully with the parsed token, or - * unsuccessfully with the failure Exception. - */ - public ApiFuture verifyIdTokenAsync(final String token, - final boolean checkRevoked) { - return new TaskToApiFuture<>(verifyIdToken(token, checkRevoked)); + private CallableOperation revokeRefreshTokensOp(final String uid) { + checkNotDestroyed(); + checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); + return new CallableOperation() { + @Override + protected Void execute() throws FirebaseAuthException { + int currentTimeSeconds = (int) (System.currentTimeMillis() / 1000); + UpdateRequest request = new UpdateRequest(uid).setValidSince(currentTimeSeconds); + userManager.updateUser(request, jsonFactory); + return null; + } + }; } /** - * Similar to {@link #getUserAsync(String)}, but returns a {@link Task}. + * Gets the user data corresponding to the specified user ID. * * @param uid A user ID string. - * @return A {@link Task} which will complete successfully with a {@link UserRecord} instance. - * If an error occurs while retrieving user data or if the specified user ID does not exist, - * the task fails with a {@link FirebaseAuthException}. + * @return A {@link UserRecord} instance. * @throws IllegalArgumentException If the user ID string is null or empty. - * @deprecated Use {@link #getUserAsync(String)} + * @throws FirebaseAuthException If an error occurs while retrieving user data. */ - public Task getUser(final String uid) { - checkNotDestroyed(); - checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); - return call(new Callable() { - @Override - public UserRecord call() throws Exception { - return userManager.getUserById(uid); - } - }); + public UserRecord getUser(@NonNull String uid) throws FirebaseAuthException { + return getUserOp(uid).call(); } /** - * Gets the user data corresponding to the specified user ID. + * Similar to {@link #getUser(String)} but performs the operation asynchronously. * * @param uid A user ID string. * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord} @@ -443,33 +534,35 @@ public UserRecord call() throws Exception { * not exist, the future throws a {@link FirebaseAuthException}. * @throws IllegalArgumentException If the user ID string is null or empty. */ - public ApiFuture getUserAsync(final String uid) { - return new TaskToApiFuture<>(getUser(uid)); + public ApiFuture getUserAsync(@NonNull String uid) { + return getUserOp(uid).callAsync(firebaseApp); + } + + private CallableOperation getUserOp(final String uid) { + checkNotDestroyed(); + checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); + return new CallableOperation() { + @Override + protected UserRecord execute() throws FirebaseAuthException { + return userManager.getUserById(uid); + } + }; } /** - * Similar to {@link #getUserByEmailAsync(String)}, but returns a {@link Task}. + * Gets the user data corresponding to the specified user email. * * @param email A user email address string. - * @return A {@link Task} which will complete successfully with a {@link UserRecord} instance. - * If an error occurs while retrieving user data or if the email address does not correspond - * to a user, the task fails with a {@link FirebaseAuthException}. + * @return A {@link UserRecord} instance. * @throws IllegalArgumentException If the email is null or empty. - * @deprecated Use {@link #getUserByEmailAsync(String)} + * @throws FirebaseAuthException If an error occurs while retrieving user data. */ - public Task getUserByEmail(final String email) { - checkNotDestroyed(); - checkArgument(!Strings.isNullOrEmpty(email), "email must not be null or empty"); - return call(new Callable() { - @Override - public UserRecord call() throws Exception { - return userManager.getUserByEmail(email); - } - }); + public UserRecord getUserByEmail(@NonNull String email) throws FirebaseAuthException { + return getUserByEmailOp(email).call(); } /** - * Gets the user data corresponding to the specified user email. + * Similar to {@link #getUserByEmail(String)} but performs the operation asynchronously. * * @param email A user email address string. * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord} @@ -477,29 +570,32 @@ public UserRecord call() throws Exception { * correspond to a user, the future throws a {@link FirebaseAuthException}. * @throws IllegalArgumentException If the email is null or empty. */ - public ApiFuture getUserByEmailAsync(final String email) { - return new TaskToApiFuture<>(getUserByEmail(email)); + public ApiFuture getUserByEmailAsync(@NonNull String email) { + return getUserByEmailOp(email).callAsync(firebaseApp); + } + + private CallableOperation getUserByEmailOp( + final String email) { + checkNotDestroyed(); + checkArgument(!Strings.isNullOrEmpty(email), "email must not be null or empty"); + return new CallableOperation() { + @Override + protected UserRecord execute() throws FirebaseAuthException { + return userManager.getUserByEmail(email); + } + }; } /** - * Similar to {@link #getUserByPhoneNumberAsync(String)}, but returns a {@link Task}. + * Gets the user data corresponding to the specified user phone number. * * @param phoneNumber A user phone number string. - * @return A {@link Task} which will complete successfully with a {@link UserRecord} instance. - * If an error occurs while retrieving user data or if the phone number does not - * correspond to a user, the task fails with a {@link FirebaseAuthException}. + * @return A a {@link UserRecord} instance. * @throws IllegalArgumentException If the phone number is null or empty. - * @deprecated Use {@link #getUserByPhoneNumberAsync(String)} + * @throws FirebaseAuthException If an error occurs while retrieving user data. */ - public Task getUserByPhoneNumber(final String phoneNumber) { - checkNotDestroyed(); - checkArgument(!Strings.isNullOrEmpty(phoneNumber), "phone number must not be null or empty"); - return call(new Callable() { - @Override - public UserRecord call() throws Exception { - return userManager.getUserByPhoneNumber(phoneNumber); - } - }); + public UserRecord getUserByPhoneNumber(@NonNull String phoneNumber) throws FirebaseAuthException { + return getUserByPhoneNumberOp(phoneNumber).call(); } /** @@ -511,20 +607,20 @@ public UserRecord call() throws Exception { * correspond to a user, the future throws a {@link FirebaseAuthException}. * @throws IllegalArgumentException If the phone number is null or empty. */ - public ApiFuture getUserByPhoneNumberAsync(final String phoneNumber) { - return new TaskToApiFuture<>(getUserByPhoneNumber(phoneNumber)); + public ApiFuture getUserByPhoneNumberAsync(@NonNull String phoneNumber) { + return getUserByPhoneNumberOp(phoneNumber).callAsync(firebaseApp); } - private Task listUsers(@Nullable String pageToken, int maxResults) { + private CallableOperation getUserByPhoneNumberOp( + final String phoneNumber) { checkNotDestroyed(); - final PageFactory factory = new PageFactory( - new DefaultUserSource(userManager, jsonFactory), maxResults, pageToken); - return call(new Callable() { + checkArgument(!Strings.isNullOrEmpty(phoneNumber), "phone number must not be null or empty"); + return new CallableOperation() { @Override - public ListUsersPage call() throws Exception { - return factory.create(); + protected UserRecord execute() throws FirebaseAuthException { + return userManager.getUserByPhoneNumber(phoneNumber); } - }); + }; } /** @@ -532,6 +628,34 @@ public ListUsersPage call() throws Exception { * limited to 1000 users. * * @param pageToken A non-empty page token string, or null to retrieve the first page of users. + * @return A {@link ListUsersPage} instance. + * @throws IllegalArgumentException If the specified page token is empty. + * @throws FirebaseAuthException If an error occurs while retrieving user data. + */ + public ListUsersPage listUsers(@Nullable String pageToken) throws FirebaseAuthException { + return listUsers(pageToken, FirebaseUserManager.MAX_LIST_USERS_RESULTS); + } + + /** + * Gets a page of users starting from the specified {@code pageToken}. + * + * @param pageToken A non-empty page token string, or null to retrieve the first page of users. + * @param maxResults Maximum number of users to include in the returned page. This may not + * exceed 1000. + * @return A {@link ListUsersPage} instance. + * @throws IllegalArgumentException If the specified page token is empty, or max results value + * is invalid. + * @throws FirebaseAuthException If an error occurs while retrieving user data. + */ + public ListUsersPage listUsers( + @Nullable String pageToken, int maxResults) throws FirebaseAuthException { + return listUsersOp(pageToken, maxResults).call(); + } + + /** + * Similar to {@link #listUsers(String)} but performs the operation asynchronously. + * + * @param pageToken A non-empty page token string, or null to retrieve the first page of users. * @return An {@code ApiFuture} which will complete successfully with a {@link ListUsersPage} * instance. If an error occurs while retrieving user data, the future throws an exception. * @throws IllegalArgumentException If the specified page token is empty. @@ -541,7 +665,7 @@ public ApiFuture listUsersAsync(@Nullable String pageToken) { } /** - * Gets a page of users starting from the specified {@code pageToken}. + * Similar to {@link #listUsers(String, int)} but performs the operation asynchronously. * * @param pageToken A non-empty page token string, or null to retrieve the first page of users. * @param maxResults Maximum number of users to include in the returned page. This may not @@ -551,30 +675,22 @@ public ApiFuture listUsersAsync(@Nullable String pageToken) { * @throws IllegalArgumentException If the specified page token is empty, or max results value * is invalid. */ - public ApiFuture listUsersAsync(@Nullable String pageToken, int maxResults) { - return new TaskToApiFuture<>(listUsers(pageToken, maxResults)); + public ApiFuture listUsersAsync( + @Nullable final String pageToken, final int maxResults) { + return listUsersOp(pageToken, maxResults).callAsync(firebaseApp); } - /** - * Similar to {@link #createUserAsync(CreateRequest)}, but returns a {@link Task}. - * - * @param request A non-null {@link CreateRequest} instance. - * @return A {@link Task} which will complete successfully with a {@link UserRecord} instance - * corresponding to the newly created account. If an error occurs while creating the user - * account, the task fails with a {@link FirebaseAuthException}. - * @throws NullPointerException if the provided request is null. - * @deprecated Use {@link #createUserAsync(CreateRequest)} - */ - public Task createUser(final CreateRequest request) { + private CallableOperation listUsersOp( + @Nullable String pageToken, int maxResults) { checkNotDestroyed(); - checkNotNull(request, "create request must not be null"); - return call(new Callable() { + final PageFactory factory = new PageFactory( + new DefaultUserSource(userManager, jsonFactory), maxResults, pageToken); + return new CallableOperation() { @Override - public UserRecord call() throws Exception { - String uid = userManager.createUser(request); - return userManager.getUserById(uid); + protected ListUsersPage execute() throws FirebaseAuthException { + return factory.create(); } - }); + }; } /** @@ -582,61 +698,77 @@ public UserRecord call() throws Exception { * {@link CreateRequest}. * * @param request A non-null {@link CreateRequest} instance. + * @return A {@link UserRecord} instance corresponding to the newly created account. + * @throws NullPointerException if the provided request is null. + * @throws FirebaseAuthException if an error occurs while creating the user account. + */ + public UserRecord createUser(@NonNull CreateRequest request) throws FirebaseAuthException { + return createUserOp(request).call(); + } + + /** + * Similar to {@link #createUser(CreateRequest)} but performs the operation asynchronously. + * + * @param request A non-null {@link CreateRequest} instance. * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord} * instance corresponding to the newly created account. If an error occurs while creating the * user account, the future throws a {@link FirebaseAuthException}. * @throws NullPointerException if the provided request is null. */ - public ApiFuture createUserAsync(final CreateRequest request) { - return new TaskToApiFuture<>(createUser(request)); + public ApiFuture createUserAsync(@NonNull CreateRequest request) { + return createUserOp(request).callAsync(firebaseApp); + } + + private CallableOperation createUserOp( + final CreateRequest request) { + checkNotDestroyed(); + checkNotNull(request, "create request must not be null"); + return new CallableOperation() { + @Override + protected UserRecord execute() throws FirebaseAuthException { + String uid = userManager.createUser(request); + return userManager.getUserById(uid); + } + }; } /** - * Similar to {@link #updateUserAsync(UpdateRequest)}, but returns a {@link Task}. + * Updates an existing user account with the attributes contained in the specified + * {@link UpdateRequest}. * * @param request A non-null {@link UpdateRequest} instance. - * @return A {@link Task} which will complete successfully with a {@link UserRecord} instance - * corresponding to the updated user account. If an error occurs while updating the user + * @return A {@link UserRecord} instance corresponding to the updated user account. * account, the task fails with a {@link FirebaseAuthException}. * @throws NullPointerException if the provided update request is null. - * @deprecated Use {@link #updateUserAsync(UpdateRequest)} + * @throws FirebaseAuthException if an error occurs while updating the user account. */ - public Task updateUser(final UpdateRequest request) { - checkNotDestroyed(); - checkNotNull(request, "update request must not be null"); - return call(new Callable() { - @Override - public UserRecord call() throws Exception { - userManager.updateUser(request, jsonFactory); - return userManager.getUserById(request.getUid()); - } - }); + public UserRecord updateUser(@NonNull UpdateRequest request) throws FirebaseAuthException { + return updateUserOp(request).call(); } /** - * Updates an existing user account with the attributes contained in the specified - * {@link UpdateRequest}. + * Similar to {@link #updateUser(UpdateRequest)} but performs the operation asynchronously. * * @param request A non-null {@link UpdateRequest} instance. * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord} * instance corresponding to the updated user account. If an error occurs while updating the * user account, the future throws a {@link FirebaseAuthException}. - * @throws NullPointerException if the provided update request is null. */ - public ApiFuture updateUserAsync(final UpdateRequest request) { - return new TaskToApiFuture<>(updateUser(request)); + public ApiFuture updateUserAsync(@NonNull UpdateRequest request) { + return updateUserOp(request).callAsync(firebaseApp); } - private Task setCustomClaims(String uid, Map claims) { + private CallableOperation updateUserOp( + final UpdateRequest request) { checkNotDestroyed(); - final UpdateRequest request = new UpdateRequest(uid).setCustomClaims(claims); - return call(new Callable() { + checkNotNull(request, "update request must not be null"); + return new CallableOperation() { @Override - public Void call() throws Exception { + protected UserRecord execute() throws FirebaseAuthException { userManager.updateUser(request, jsonFactory); - return null; + return userManager.getUserById(request.getUid()); } - }); + }; } /** @@ -646,53 +778,78 @@ public Void call() throws Exception { * * @param uid A user ID string. * @param claims A map of custom claims or null. - * @return An {@code ApiFuture} which will complete successfully when the user account has been - * updated. If an error occurs while deleting the user account, the future throws a - * {@link FirebaseAuthException}. + * @throws FirebaseAuthException If an error occurs while updating custom claims. * @throws IllegalArgumentException If the user ID string is null or empty, or the claims * payload is invalid or too large. */ - public ApiFuture setCustomUserClaimsAsync(String uid, Map claims) { - return new TaskToApiFuture<>(setCustomClaims(uid, claims)); + public void setCustomClaims(@NonNull String uid, + @Nullable Map claims) throws FirebaseAuthException { + setCustomUserClaimsOp(uid, claims).call(); } /** - * Similar to {@link #deleteUserAsync(String)}, but returns a {@link Task}. + * Similar to {@link #setCustomClaims(String, Map)} but performs the operation asynchronously. * * @param uid A user ID string. - * @return A {@link Task} which will complete successfully when the specified user account has - * been deleted. If an error occurs while deleting the user account, the task fails with a + * @param claims A map of custom claims or null. + * @return An {@code ApiFuture} which will complete successfully when the user account has been + * updated. If an error occurs while deleting the user account, the future throws a * {@link FirebaseAuthException}. * @throws IllegalArgumentException If the user ID string is null or empty. - * @deprecated Use {@link #deleteUserAsync(String)} */ - public Task deleteUser(final String uid) { + public ApiFuture setCustomUserClaimsAsync( + @NonNull String uid, @Nullable Map claims) { + return setCustomUserClaimsOp(uid, claims).callAsync(firebaseApp); + } + + private CallableOperation setCustomUserClaimsOp( + final String uid, final Map claims) { checkNotDestroyed(); checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); - return call(new Callable() { + return new CallableOperation() { @Override - public Void call() throws Exception { - userManager.deleteUser(uid); + protected Void execute() throws FirebaseAuthException { + final UpdateRequest request = new UpdateRequest(uid).setCustomClaims(claims); + userManager.updateUser(request, jsonFactory); return null; } - }); + }; } /** * Deletes the user identified by the specified user ID. * * @param uid A user ID string. + * @throws IllegalArgumentException If the user ID string is null or empty. + * @throws FirebaseAuthException If an error occurs while deleting the user. + */ + public void deleteUser(@NonNull String uid) throws FirebaseAuthException { + deleteUserOp(uid).call(); + } + + /** + * Similar to {@link #deleteUser(String)} but performs the operation asynchronously. + * + * @param uid A user ID string. * @return An {@code ApiFuture} which will complete successfully when the specified user account * has been deleted. If an error occurs while deleting the user account, the future throws a * {@link FirebaseAuthException}. * @throws IllegalArgumentException If the user ID string is null or empty. */ public ApiFuture deleteUserAsync(final String uid) { - return new TaskToApiFuture<>(deleteUser(uid)); + return deleteUserOp(uid).callAsync(firebaseApp); } - private Task call(Callable command) { - return ImplFirebaseTrampolines.submitCallable(firebaseApp, command); + private CallableOperation deleteUserOp(final String uid) { + checkNotDestroyed(); + checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); + return new CallableOperation() { + @Override + protected Void execute() throws FirebaseAuthException { + userManager.deleteUser(uid); + return null; + } + }; } @VisibleForTesting diff --git a/src/main/java/com/google/firebase/auth/FirebaseCredential.java b/src/main/java/com/google/firebase/auth/FirebaseCredential.java deleted file mode 100644 index b238d299c..000000000 --- a/src/main/java/com/google/firebase/auth/FirebaseCredential.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.auth; - -import com.google.firebase.tasks.Task; - -/** - * Provides Google OAuth2 access tokens used to authenticate with Firebase services. In most cases, - * you will not need to implement this yourself and can instead use the default implementations - * provided by {@link FirebaseCredentials}. - * - * @deprecated Use {@code GoogleCredentials}. - */ -public interface FirebaseCredential { - - /** - * Returns a Google OAuth2 access token which can be used to authenticate with Firebase services. - * This method does not cache tokens, and therefore each invocation will fetch a fresh token. - * The caller is expected to implement caching by referencing the token expiry details - * available in the returned GoogleOAuthAccessToken instance. - * - * @return A {@link Task} providing a Google OAuth access token. - */ - Task getAccessToken(); -} diff --git a/src/main/java/com/google/firebase/auth/FirebaseCredentials.java b/src/main/java/com/google/firebase/auth/FirebaseCredentials.java deleted file mode 100644 index 4ae1d62da..000000000 --- a/src/main/java/com/google/firebase/auth/FirebaseCredentials.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.auth; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.api.client.googleapis.util.Utils; -import com.google.api.client.http.HttpTransport; -import com.google.api.client.json.JsonFactory; -import com.google.auth.http.HttpTransportFactory; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.auth.oauth2.ServiceAccountCredentials; -import com.google.auth.oauth2.UserCredentials; -import com.google.common.base.Strings; -import com.google.firebase.auth.internal.BaseCredential; -import com.google.firebase.internal.NonNull; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Standard {@link FirebaseCredential} implementations for use with {@link - * com.google.firebase.FirebaseOptions}. - * - * @deprecated Use {@code GoogleCredentials}. - */ -public class FirebaseCredentials { - - private FirebaseCredentials() { - } - - /** - * Returns a {@link FirebaseCredential} based on Google Application Default Credentials which can - * be used to authenticate the SDK. - * - *

See Google - * Application Default Credentials for details on Google Application Deafult Credentials. - * - *

See Initialize the SDK for code samples - * and detailed documentation. - * - * @return A {@link FirebaseCredential} based on Google Application Default Credentials which can - * be used to authenticate the SDK. - */ - @NonNull - public static FirebaseCredential applicationDefault() { - return DefaultCredentialsHolder.INSTANCE; - } - - /** - * Returns a {@link FirebaseCredential} based on Google Application Default Credentials which can - * be used to authenticate the SDK. Allows specifying the HttpTransport and the - * JsonFactory to be used when communicating with the remote authentication server. - * - *

See Google - * Application Default Credentials for details on Google Application Deafult Credentials. - * - *

See Initialize the SDK for code samples - * and detailed documentation. - * - * @param transport HttpTransport used to communicate with the remote - * authentication server. - * @param jsonFactory JsonFactory used to parse JSON responses from the remote - * authentication server. - * @return A {@link FirebaseCredential} based on Google Application Default Credentials which can - * be used to authenticate the SDK. - */ - @NonNull - public static FirebaseCredential applicationDefault( - HttpTransport transport, JsonFactory jsonFactory) { - try { - return new ApplicationDefaultCredential(transport); - } catch (IOException e) { - // To prevent a breaking API change, we throw an unchecked exception. - throw new RuntimeException(e); - } - } - - /** - * Returns a {@link FirebaseCredential} generated from the provided service account certificate - * which can be used to authenticate the SDK. - * - *

See Initialize the SDK for code samples - * and detailed documentation. - * - * @param serviceAccount An InputStream containing the JSON representation of a - * service account certificate. - * @return A {@link FirebaseCredential} generated from the provided service account certificate - * which can be used to authenticate the SDK. - * @throws IOException If an error occurs while parsing the service account certificate. - */ - @NonNull - public static FirebaseCredential fromCertificate(InputStream serviceAccount) throws IOException { - return fromCertificate(serviceAccount, Utils.getDefaultTransport(), - Utils.getDefaultJsonFactory()); - } - - /** - * Returns a {@link FirebaseCredential} generated from the provided service account certificate - * which can be used to authenticate the SDK. Allows specifying the HttpTransport - * and the JsonFactory to be used when communicating with the remote authentication - * server. - * - *

See Initialize the SDK for code samples - * and detailed documentation. - * - * @param serviceAccount An InputStream containing the JSON representation of a - * service account certificate. - * @param transport HttpTransport used to communicate with the remote - * authentication server. - * @param jsonFactory JsonFactory used to parse JSON responses from the remote - * authentication server. - * @return A {@link FirebaseCredential} generated from the provided service account certificate - * which can be used to authenticate the SDK. - * @throws IOException If an error occurs while parsing the service account certificate. - */ - @NonNull - public static FirebaseCredential fromCertificate(InputStream serviceAccount, - HttpTransport transport, JsonFactory jsonFactory) throws IOException { - ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream( - serviceAccount, wrap(transport)); - checkArgument(!Strings.isNullOrEmpty(credentials.getProjectId()), - "Failed to parse service account: 'project_id' must be set"); - return new CertCredential(credentials); - } - - /** - * Returns a {@link FirebaseCredential} generated from the provided refresh token which can be - * used to authenticate the SDK. - * - *

See Initialize the SDK for code samples - * and detailed documentation. - * - * @param refreshToken An InputStream containing the JSON representation of a refresh - * token. - * @return A {@link FirebaseCredential} generated from the provided service account credential - * which can be used to authenticate the SDK. - * @throws IOException If an error occurs while parsing the refresh token. - */ - @NonNull - public static FirebaseCredential fromRefreshToken(InputStream refreshToken) throws IOException { - return fromRefreshToken( - refreshToken, Utils.getDefaultTransport(), Utils.getDefaultJsonFactory()); - } - - /** - * Returns a {@link FirebaseCredential} generated from the provided refresh token which can be - * used to authenticate the SDK. Allows specifying the HttpTransport and the - * JsonFactory to be used when communicating with the remote authentication server. - * - *

See Initialize the SDK for code samples - * and detailed documentation. - * - * @param refreshToken An InputStream containing the JSON representation of a refresh - * token. - * @param transport HttpTransport used to communicate with the remote - * authentication server. - * @param jsonFactory JsonFactory used to parse JSON responses from the remote - * authentication server. - * @return A {@link FirebaseCredential} generated from the provided service account credential - * which can be used to authenticate the SDK. - * @throws IOException If an error occurs while parsing the refresh token. - */ - @NonNull - public static FirebaseCredential fromRefreshToken(final InputStream refreshToken, - HttpTransport transport, JsonFactory jsonFactory) throws IOException { - return new RefreshTokenCredential(refreshToken, transport); - } - - static class CertCredential extends BaseCredential { - - CertCredential(ServiceAccountCredentials credentials) throws IOException { - super(credentials); - } - } - - static class ApplicationDefaultCredential extends BaseCredential { - - ApplicationDefaultCredential(HttpTransport transport) throws IOException { - super(GoogleCredentials.getApplicationDefault(wrap(transport))); - } - } - - static class RefreshTokenCredential extends BaseCredential { - - RefreshTokenCredential(InputStream inputStream, HttpTransport transport) throws IOException { - super(UserCredentials.fromStream(inputStream, wrap(transport))); - } - } - - private static class DefaultCredentialsHolder { - - static final FirebaseCredential INSTANCE = - applicationDefault(Utils.getDefaultTransport(), Utils.getDefaultJsonFactory()); - } - - private static HttpTransportFactory wrap(final HttpTransport transport) { - checkNotNull(transport, "HttpTransport must not be null"); - return new HttpTransportFactory() { - @Override - public HttpTransport create() { - return transport; - } - }; - } -} diff --git a/src/main/java/com/google/firebase/auth/GoogleOAuthAccessToken.java b/src/main/java/com/google/firebase/auth/GoogleOAuthAccessToken.java deleted file mode 100644 index 85e6021e3..000000000 --- a/src/main/java/com/google/firebase/auth/GoogleOAuthAccessToken.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.auth; - -import static com.google.common.base.Preconditions.checkArgument; - -import com.google.common.base.Strings; - -/** - * Represents an OAuth access token, which can be used to access Firebase and other qualified - * Google APIs. Encapsulates both the token string, and its expiration time. - * - * @deprecated Use GoogleCredentials and associated classes. - */ -public class GoogleOAuthAccessToken { - - - private final String accessToken; - private final long expiryTime; - - /** - * Create a new GoogleOAuthAccessToken instance - * - * @param accessToken JWT access token string - * @param expiryTime Time at which the token will expire (milliseconds since epoch) - * @throws IllegalArgumentException If the token is null or empty - */ - public GoogleOAuthAccessToken(String accessToken, long expiryTime) { - checkArgument(!Strings.isNullOrEmpty(accessToken), "Access token must not be null"); - this.accessToken = accessToken; - this.expiryTime = expiryTime; - } - - /** - * Returns the JWT access token. - */ - public String getAccessToken() { - return accessToken; - } - - /** - * Returns the expiration time as a milliseconds since epoch timestamp. - */ - public long getExpiryTime() { - return expiryTime; - } - -} diff --git a/src/main/java/com/google/firebase/auth/internal/BaseCredential.java b/src/main/java/com/google/firebase/auth/internal/BaseCredential.java deleted file mode 100644 index d15832a55..000000000 --- a/src/main/java/com/google/firebase/auth/internal/BaseCredential.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.auth.internal; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.auth.oauth2.AccessToken; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.common.collect.ImmutableList; -import com.google.firebase.auth.FirebaseCredential; -import com.google.firebase.auth.GoogleOAuthAccessToken; -import com.google.firebase.tasks.Task; -import com.google.firebase.tasks.Tasks; - -import java.io.IOException; -import java.util.List; - -/** - * Internal base class for built-in FirebaseCredential implementations. - */ -public abstract class BaseCredential implements FirebaseCredential { - - public static final List FIREBASE_SCOPES = - ImmutableList.of( - // Enables access to Firebase Realtime Database. - "https://www.googleapis.com/auth/firebase.database", - - // Enables access to the email address associated with a project. - "https://www.googleapis.com/auth/userinfo.email", - - // Enables access to Google Identity Toolkit (for user management APIs). - "https://www.googleapis.com/auth/identitytoolkit", - - // Enables access to Google Cloud Storage. - "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; - - public BaseCredential(GoogleCredentials googleCredentials) { - this.googleCredentials = checkNotNull(googleCredentials).createScoped(FIREBASE_SCOPES); - } - - public final GoogleCredentials getGoogleCredentials() { - return googleCredentials; - } - - @Override - public Task getAccessToken() { - try { - AccessToken accessToken = googleCredentials.refreshAccessToken(); - GoogleOAuthAccessToken googleToken = new GoogleOAuthAccessToken(accessToken.getTokenValue(), - accessToken.getExpirationTime().getTime()); - return Tasks.forResult(googleToken); - } catch (Exception e) { - return Tasks.forException(e); - } - } - -} diff --git a/src/main/java/com/google/firebase/auth/internal/FirebaseCredentialsAdapter.java b/src/main/java/com/google/firebase/auth/internal/FirebaseCredentialsAdapter.java deleted file mode 100644 index e1b069eda..000000000 --- a/src/main/java/com/google/firebase/auth/internal/FirebaseCredentialsAdapter.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.auth.internal; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.auth.oauth2.AccessToken; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.firebase.auth.FirebaseCredential; -import com.google.firebase.auth.GoogleOAuthAccessToken; -import com.google.firebase.tasks.Tasks; -import java.io.IOException; -import java.util.Date; -import java.util.concurrent.ExecutionException; - -/** - * An adapter for converting custom {@link FirebaseCredential} implementations into - * GoogleCredentials. - */ -public final class FirebaseCredentialsAdapter extends GoogleCredentials { - - private final FirebaseCredential credential; - - public FirebaseCredentialsAdapter(FirebaseCredential credential) { - this.credential = checkNotNull(credential); - } - - @Override - public AccessToken refreshAccessToken() throws IOException { - try { - GoogleOAuthAccessToken token = Tasks.await(credential.getAccessToken()); - return new AccessToken(token.getAccessToken(), new Date(token.getExpiryTime())); - } catch (ExecutionException | InterruptedException e) { - throw new IOException("Error while obtaining OAuth2 token", e); - } - } -} diff --git a/src/main/java/com/google/firebase/database/ChildEventListener.java b/src/main/java/com/google/firebase/database/ChildEventListener.java index 1a1bb24d1..65bcdfdce 100644 --- a/src/main/java/com/google/firebase/database/ChildEventListener.java +++ b/src/main/java/com/google/firebase/database/ChildEventListener.java @@ -53,7 +53,7 @@ public interface ChildEventListener { /** * This method is triggered when a child location's priority changes. See {@link - * DatabaseReference#setPriority(Object)} and Ordered Data for more information on priorities and ordering data. * diff --git a/src/main/java/com/google/firebase/database/DataSnapshot.java b/src/main/java/com/google/firebase/database/DataSnapshot.java index 700fa2a2a..f1f470505 100644 --- a/src/main/java/com/google/firebase/database/DataSnapshot.java +++ b/src/main/java/com/google/firebase/database/DataSnapshot.java @@ -38,7 +38,7 @@ * They are efficiently-generated immutable copies of the data at a Firebase Database location. They * can't be modified and will never change. To modify data at a location, use a {@link * DatabaseReference DatabaseReference} reference (e.g. with {@link - * DatabaseReference#setValue(Object)}). + * DatabaseReference#setValueAsync(Object)}). */ public class DataSnapshot { @@ -114,7 +114,7 @@ public boolean exists() { *

  • List<Object> * * - *

    This list is recursive; the possible types for {@link java.lang.Object} in the above list + *

    This list is recursive; the possible types for Object in the above list * is given by the same list. These types correspond to the types available in JSON. * * @return The data contained in this snapshot as native types or null if there is no data at this @@ -138,7 +138,7 @@ public Object getValue() { *

  • List<Object> * * - *

    This list is recursive; the possible types for {@link java.lang.Object} in the above list is + *

    This list is recursive; the possible types for Object in the above list is * given by the same list. These types correspond to the types available in JSON. * *

    If useExportFormat is set to true, priority information will be included in the output. @@ -206,7 +206,7 @@ public T getValue(Class valueType) { /** * Due to the way that Java implements generics, it takes an extra step to get back a - * properly-typed Collection. So, in the case where you want a {@link java.util.List} of Message + * properly-typed Collection. So, in the case where you want a List of Message * instances, you will need to do something like the following: * *

    
    diff --git a/src/main/java/com/google/firebase/database/DatabaseReference.java b/src/main/java/com/google/firebase/database/DatabaseReference.java
    index 1c1b9db71..10dfe40ba 100644
    --- a/src/main/java/com/google/firebase/database/DatabaseReference.java
    +++ b/src/main/java/com/google/firebase/database/DatabaseReference.java
    @@ -33,8 +33,6 @@
     import com.google.firebase.database.utilities.Utilities;
     import com.google.firebase.database.utilities.Validation;
     import com.google.firebase.database.utilities.encoding.CustomClassMapper;
    -import com.google.firebase.internal.TaskToApiFuture;
    -import com.google.firebase.tasks.Task;
     
     import java.io.UnsupportedEncodingException;
     import java.net.URLEncoder;
    @@ -177,7 +175,7 @@ public DatabaseReference push() {
        * @return The ApiFuture for this operation.
        */
       public ApiFuture setValueAsync(Object value) {
    -    return new TaskToApiFuture<>(setValue(value));
    +    return setValueInternal(value, PriorityUtilities.parsePriority(this.path, null), null);
       }
     
       /**
    @@ -215,29 +213,6 @@ public ApiFuture setValueAsync(Object value) {
        * @return The ApiFuture for this operation.
        */
       public ApiFuture setValueAsync(Object value, Object priority) {
    -    return new TaskToApiFuture<>(setValue(value, priority));
    -  }
    -
    -  /**
    -   * Similar to {@link #setValueAsync(Object)} but returns a Task.
    -   *
    -   * @param value The value to set at this location
    -   * @return The {@link Task} for this operation.
    -   * @deprecated Use {@link #setValueAsync(Object)}
    -   */
    -  public Task setValue(Object value) {
    -    return setValueInternal(value, PriorityUtilities.parsePriority(this.path, null), null);
    -  }
    -
    -  /**
    -   * Similar to {@link #setValueAsync(Object, Object)} but returns a Task.
    -   *
    -   * @param value The value to set at this location
    -   * @param priority The priority to set at this location
    -   * @return The {@link Task} for this operation.
    -   * @deprecated Use {@link #setValueAsync(Object, Object)}
    -   */
    -  public Task setValue(Object value, Object priority) {
         return setValueInternal(value, PriorityUtilities.parsePriority(this.path, priority), null);
       }
     
    @@ -315,13 +290,14 @@ public void setValue(Object value, Object priority, CompletionListener listener)
         setValueInternal(value, PriorityUtilities.parsePriority(this.path, priority), listener);
       }
     
    -  private Task setValueInternal(Object value, Node priority, CompletionListener optListener) {
    +  private ApiFuture setValueInternal(Object value, Node priority, CompletionListener
    +      optListener) {
         Validation.validateWritablePath(getPath());
         ValidationPath.validateWithObject(getPath(), value);
         Object bouncedValue = CustomClassMapper.convertToPlainJavaTypes(value);
         Validation.validateWritableObject(bouncedValue);
         final Node node = NodeUtilities.NodeFromJSON(bouncedValue, priority);
    -    final Pair, CompletionListener> wrapped = Utilities.wrapOnComplete(optListener);
    +    final Pair, CompletionListener> wrapped = Utilities.wrapOnComplete(optListener);
         repo.scheduleNow(
             new Runnable() {
               @Override
    @@ -364,17 +340,6 @@ public void run() {
        * @return The ApiFuture for this operation.
        */
       public ApiFuture setPriorityAsync(Object priority) {
    -    return new TaskToApiFuture<>(setPriority(priority));
    -  }
    -
    -  /**
    -   * Similar to {@link #setPriorityAsync(Object)} but returns a Task.
    -   *
    -   * @param priority The priority to set at the specified location.
    -   * @return The {@link Task} for this operation.
    -   * @deprecated Use {@link #setPriorityAsync(Object)}
    -   */
    -  public Task setPriority(Object priority) {
         return setPriorityInternal(PriorityUtilities.parsePriority(this.path, priority), null);
       }
     
    @@ -413,10 +378,10 @@ public void setPriority(Object priority, CompletionListener listener) {
     
       // Remove
     
    -  private Task setPriorityInternal(final Node priority, CompletionListener optListener) {
    +  private ApiFuture setPriorityInternal(final Node priority, CompletionListener optListener) {
         Validation.validateWritablePath(getPath());
     
    -    final Pair, CompletionListener> wrapped = Utilities.wrapOnComplete(optListener);
    +    final Pair, CompletionListener> wrapped = Utilities.wrapOnComplete(optListener);
         repo.scheduleNow(
             new Runnable() {
               @Override
    @@ -438,17 +403,6 @@ public void run() {
        * @return The ApiFuture for this operation.
        */
       public ApiFuture updateChildrenAsync(Map update) {
    -    return new TaskToApiFuture<>(updateChildren(update));
    -  }
    -
    -  /**
    -   * Similar to {@link #updateChildrenAsync(Map)} but returns a Task.
    -   *
    -   * @param update The paths to update and their new values
    -   * @return The {@link Task} for this operation.
    -   * @deprecated Use {@link #updateChildrenAsync(Map)}
    -   */
    -  public Task updateChildren(Map update) {
         return updateChildrenInternal(update, null);
       }
     
    @@ -467,7 +421,7 @@ public void updateChildren(final Map update, final CompletionLis
     
       // Transactions
     
    -  private Task updateChildrenInternal(
    +  private ApiFuture updateChildrenInternal(
           final Map update, final CompletionListener optListener) {
         if (update == null) {
           throw new NullPointerException("Can't pass null for argument 'update' in updateChildren()");
    @@ -477,7 +431,7 @@ private Task updateChildrenInternal(
             Validation.parseAndValidateUpdate(getPath(), bouncedUpdate);
         final CompoundWrite merge = CompoundWrite.fromPathMerge(parsedUpdate);
     
    -    final Pair, CompletionListener> wrapped = Utilities.wrapOnComplete(optListener);
    +    final Pair, CompletionListener> wrapped = Utilities.wrapOnComplete(optListener);
         repo.scheduleNow(
             new Runnable() {
               @Override
    @@ -494,17 +448,7 @@ public void run() {
        * @return The ApiFuture for this operation.
        */
       public ApiFuture removeValueAsync() {
    -    return new TaskToApiFuture<>(removeValue());
    -  }
    -
    -  /**
    -   * Similar to {@link #removeValueAsync()} but returns a Task.
    -   *
    -   * @return The Task for this operation.
    -   * @deprecated Use {@link #removeValueAsync()}
    -   */
    -  public Task removeValue() {
    -    return setValue(null);
    +    return setValueAsync(null);
       }
     
       // Manual Connection Management
    diff --git a/src/main/java/com/google/firebase/database/FirebaseDatabase.java b/src/main/java/com/google/firebase/database/FirebaseDatabase.java
    index 6a196f913..2ddb55d58 100644
    --- a/src/main/java/com/google/firebase/database/FirebaseDatabase.java
    +++ b/src/main/java/com/google/firebase/database/FirebaseDatabase.java
    @@ -256,24 +256,6 @@ public void goOffline() {
         RepoManager.interrupt(ensureRepo());
       }
     
    -  /**
    -   * By default, this is set to {@link Logger.Level#INFO INFO}. This includes any internal errors
    -   * ({@link Logger.Level#ERROR ERROR}) and any security debug messages ({@link Logger.Level#INFO
    -   * INFO}) that the client receives. Set to {@link Logger.Level#DEBUG DEBUG} to turn on the
    -   * diagnostic logging, and {@link Logger.Level#NONE NONE} to disable all logging.
    -   *
    -   * @param logLevel The desired minimum log level
    -   * @deprecated This method will be removed in a future release. Use SLF4J-based logging instead.
    -   *     For example, add the slf4j-simple.jar to the classpath to log to STDERR. See
    -   *     SLF4J user manual for more details.
    -   */
    -  public synchronized void setLogLevel(Logger.Level logLevel) {
    -    synchronized (lock) {
    -      assertUnfrozen("setLogLevel");
    -      this.config.setLogLevel(logLevel);
    -    }
    -  }
    -
       /**
        * The Firebase Database client will cache synchronized data and keep track of all writes you've
        * initiated while your application is running. It seamlessly handles intermittent network
    diff --git a/src/main/java/com/google/firebase/database/MutableData.java b/src/main/java/com/google/firebase/database/MutableData.java
    index 79e87f2b4..7a0e7983b 100644
    --- a/src/main/java/com/google/firebase/database/MutableData.java
    +++ b/src/main/java/com/google/firebase/database/MutableData.java
    @@ -177,7 +177,7 @@ public String getKey() {
        *   
  • List<Object> * * - *

    This list is recursive; the possible types for {@link java.lang.Object} in the above list is + *

    This list is recursive; the possible types for Object in the above list is * given by the same list. These types correspond to the types available in JSON. * * @return The data contained in this instance as native types, or null if there is no data at @@ -190,7 +190,7 @@ public Object getValue() { /** * Due to the way that Java implements generics, it takes an extra step to get back a - * properly-typed Collection. So, in the case where you want a {@link java.util.List} of Message + * properly-typed Collection. So, in the case where you want a List of Message * instances, you will need to do something like the following: * *

    
    diff --git a/src/main/java/com/google/firebase/database/OnDisconnect.java b/src/main/java/com/google/firebase/database/OnDisconnect.java
    index 3b02987db..7b4cc0859 100644
    --- a/src/main/java/com/google/firebase/database/OnDisconnect.java
    +++ b/src/main/java/com/google/firebase/database/OnDisconnect.java
    @@ -28,8 +28,6 @@
     import com.google.firebase.database.utilities.Utilities;
     import com.google.firebase.database.utilities.Validation;
     import com.google.firebase.database.utilities.encoding.CustomClassMapper;
    -import com.google.firebase.internal.TaskToApiFuture;
    -import com.google.firebase.tasks.Task;
     
     import java.util.Map;
     
    @@ -52,41 +50,6 @@ public class OnDisconnect {
         this.path = path;
       }
     
    -  /**
    -   * Similar to {@link #setValueAsync(Object)}, but returns a Task.
    -   *
    -   * @param value The value to be set when a disconnect occurs
    -   * @return The {@link Task} for this operation.
    -   * @deprecated Use {@link #setValueAsync(Object)}
    -   */
    -  public Task setValue(Object value) {
    -    return onDisconnectSetInternal(value, PriorityUtilities.NullPriority(), null);
    -  }
    -
    -  /**
    -   * Similar to {@link #setValueAsync(Object, String)}, but returns a Task.
    -   *
    -   * @param value The value to be set when a disconnect occurs
    -   * @param priority The priority to be set when a disconnect occurs
    -   * @return The {@link Task} for this operation.
    -   * @deprecated Use {@link #setValueAsync(Object, String)}
    -   */
    -  public Task setValue(Object value, String priority) {
    -    return onDisconnectSetInternal(value, PriorityUtilities.parsePriority(path, priority), null);
    -  }
    -
    -  /**
    -   * Similar to {@link #setValueAsync(Object, double)}, but returns a Task.
    -   *
    -   * @param value The value to be set when a disconnect occurs
    -   * @param priority The priority to be set when a disconnect occurs
    -   * @return The {@link Task} for this operation.
    -   * @deprecated Use {@link #setValueAsync(Object, double)}
    -   */
    -  public Task setValue(Object value, double priority) {
    -    return onDisconnectSetInternal(value, PriorityUtilities.parsePriority(path, priority), null);
    -  }
    -
       /**
        * Ensure the data at this location is set to the specified value when the client is disconnected
        * (due to closing the browser, navigating to a new page, or network issues). 
    @@ -157,7 +120,7 @@ public void setValue(Object value, Map priority, CompletionListener listener) { * @return The ApiFuture for this operation. */ public ApiFuture setValueAsync(Object value) { - return new TaskToApiFuture<>(setValue(value)); + return onDisconnectSetInternal(value, PriorityUtilities.NullPriority(), null); } /** @@ -172,7 +135,7 @@ public ApiFuture setValueAsync(Object value) { * @return The ApiFuture for this operation. */ public ApiFuture setValueAsync(Object value, String priority) { - return new TaskToApiFuture<>(setValue(value, priority)); + return onDisconnectSetInternal(value, PriorityUtilities.parsePriority(path, priority), null); } /** @@ -187,17 +150,17 @@ public ApiFuture setValueAsync(Object value, String priority) { * @return The ApiFuture for this operation. */ public ApiFuture setValueAsync(Object value, double priority) { - return new TaskToApiFuture<>(setValue(value, priority)); + return onDisconnectSetInternal(value, PriorityUtilities.parsePriority(path, priority), null); } - private Task onDisconnectSetInternal( + private ApiFuture onDisconnectSetInternal( Object value, Node priority, final CompletionListener optListener) { Validation.validateWritablePath(path); ValidationPath.validateWithObject(path, value); Object bouncedValue = CustomClassMapper.convertToPlainJavaTypes(value); Validation.validateWritableObject(bouncedValue); final Node node = NodeUtilities.NodeFromJSON(bouncedValue, priority); - final Pair, CompletionListener> wrapped = Utilities.wrapOnComplete(optListener); + final Pair, CompletionListener> wrapped = Utilities.wrapOnComplete(optListener); repo.scheduleNow( new Runnable() { @Override @@ -210,17 +173,6 @@ public void run() { // Update - /** - * Similar to {@link #updateChildrenAsync(Map)}, but returns a Task. - * - * @param update The paths to update, along with their desired values - * @return The {@link Task} for this operation. - * @deprecated Use {@link #updateChildrenAsync(Map)} - */ - public Task updateChildren(Map update) { - return updateChildrenInternal(update, null); - } - /** * Ensure the data has the specified child values updated when the client is disconnected * @@ -238,13 +190,13 @@ public void updateChildren(final Map update, final CompletionLis * @return The ApiFuture for this operation. */ public ApiFuture updateChildrenAsync(Map update) { - return new TaskToApiFuture<>(updateChildren(update)); + return updateChildrenInternal(update, null); } - private Task updateChildrenInternal( + private ApiFuture updateChildrenInternal( final Map update, final CompletionListener optListener) { final Map parsedUpdate = Validation.parseAndValidateUpdate(path, update); - final Pair, CompletionListener> wrapped = Utilities.wrapOnComplete(optListener); + final Pair, CompletionListener> wrapped = Utilities.wrapOnComplete(optListener); repo.scheduleNow( new Runnable() { @Override @@ -257,16 +209,6 @@ public void run() { // Remove - /** - * Similar to {@link #removeValueAsync()}, but returns a Task. - * - * @return The {@link Task} for this operation. - * @deprecated Use {@link #removeValueAsync()} - */ - public Task removeValue() { - return setValue(null); - } - /** * Remove the value at this location when the client disconnects * @@ -282,21 +224,11 @@ public void removeValue(CompletionListener listener) { * @return The ApiFuture for this operation. */ public ApiFuture removeValueAsync() { - return new TaskToApiFuture<>(removeValue()); + return setValueAsync(null); } // Cancel the operation - /** - * Similar to {@link #cancelAsync()} ()}, but returns a Task. - * - * @return The {@link Task} for this operation. - * @deprecated Use {@link #cancelAsync()}. - */ - public Task cancel() { - return cancelInternal(null); - } - /** * Cancel any disconnect operations that are queued up at this location * @@ -312,11 +244,11 @@ public void cancel(final CompletionListener listener) { * @return The ApiFuture for this operation. */ public ApiFuture cancelAsync() { - return new TaskToApiFuture<>(cancel()); + return cancelInternal(null); } - private Task cancelInternal(final CompletionListener optListener) { - final Pair, CompletionListener> wrapped = Utilities.wrapOnComplete(optListener); + private ApiFuture cancelInternal(final CompletionListener optListener) { + final Pair, CompletionListener> wrapped = Utilities.wrapOnComplete(optListener); repo.scheduleNow( new Runnable() { @Override diff --git a/src/main/java/com/google/firebase/database/connection/Connection.java b/src/main/java/com/google/firebase/database/connection/Connection.java index 4059e46e8..83e377304 100644 --- a/src/main/java/com/google/firebase/database/connection/Connection.java +++ b/src/main/java/com/google/firebase/database/connection/Connection.java @@ -17,10 +17,11 @@ package com.google.firebase.database.connection; import com.google.common.annotations.VisibleForTesting; -import com.google.firebase.database.logging.LogWrapper; import java.util.HashMap; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class Connection implements WebsocketConnection.Delegate { @@ -39,11 +40,14 @@ class Connection implements WebsocketConnection.Delegate { private static final String SERVER_HELLO_TIMESTAMP = "ts"; private static final String SERVER_HELLO_HOST = "h"; private static final String SERVER_HELLO_SESSION_ID = "s"; + + private static final Logger logger = LoggerFactory.getLogger(Connection.class); + private static long connectionIds = 0; - private final LogWrapper logger; private final HostInfo hostInfo; private final Delegate delegate; + private final String label; private WebsocketConnection conn; private State state; @@ -54,36 +58,31 @@ class Connection implements WebsocketConnection.Delegate { String cachedHost, Delegate delegate, String optLastSessionId) { - this(context, hostInfo, delegate, + this(hostInfo, delegate, new DefaultWebsocketConnectionFactory(context, hostInfo, cachedHost, optLastSessionId)); } @VisibleForTesting Connection( - ConnectionContext context, HostInfo hostInfo, Delegate delegate, WebsocketConnectionFactory connFactory) { long connId = connectionIds++; this.hostInfo = hostInfo; this.delegate = delegate; - this.logger = new LogWrapper(context.getLogger(), Connection.class, "conn_" + connId); + this.label = "[conn_" + connId + "]"; this.state = State.REALTIME_CONNECTING; this.conn = connFactory.newConnection(this); } public void open() { - if (logger.logsDebug()) { - logger.debug("Opening a connection"); - } + logger.debug("{} Opening a connection", label); conn.open(); } public void close(DisconnectReason reason) { if (state != State.REALTIME_DISCONNECTED) { - if (logger.logsDebug()) { - logger.debug("closing realtime connection"); - } + logger.debug("{} Closing realtime connection", label); state = State.REALTIME_DISCONNECTED; if (conn != null) { @@ -123,21 +122,14 @@ public void onMessage(Map message) { Map data = (Map) message.get(SERVER_ENVELOPE_DATA); onControlMessage(data); } else { - if (logger.logsDebug()) { - logger.debug("Ignoring unknown server message type: " + messageType); - } + logger.debug("{} Ignoring unknown server message type: {}", label, messageType); } } else { - if (logger.logsDebug()) { - logger.debug( - "Failed to parse server message: missing message type:" + message.toString()); - } + logger.debug("{} Failed to parse server message: missing message type: {}", label, message); close(); } } catch (ClassCastException e) { - if (logger.logsDebug()) { - logger.debug("Failed to parse server message: " + e.toString()); - } + logger.debug("{} Failed to parse server message", label, e); close(); } } @@ -146,30 +138,22 @@ public void onMessage(Map message) { public void onDisconnect(boolean wasEverConnected) { conn = null; if (!wasEverConnected && state == State.REALTIME_CONNECTING) { - if (logger.logsDebug()) { - logger.debug("Realtime connection failed"); - } + logger.debug("{} Realtime connection failed", label); } else { - if (logger.logsDebug()) { - logger.debug("Realtime connection lost"); - } + logger.debug("{} Realtime connection lost", label); } close(); } private void onDataMessage(Map data) { - if (logger.logsDebug()) { - logger.debug("received data message: " + data.toString()); - } + logger.debug("{} Received data message: {}", label, data); // We don't do anything with data messages, just kick them up a level delegate.onDataMessage(data); } private void onControlMessage(Map data) { - if (logger.logsDebug()) { - logger.debug("Got control message: " + data.toString()); - } + logger.debug("{} Got control message: {}", label, data); try { String messageType = (String) data.get(SERVER_CONTROL_MESSAGE_TYPE); if (messageType != null) { @@ -185,28 +169,20 @@ private void onControlMessage(Map data) { (Map) data.get(SERVER_CONTROL_MESSAGE_DATA); onHandshake(handshakeData); } else { - if (logger.logsDebug()) { - logger.debug("Ignoring unknown control message: " + messageType); - } + logger.debug("{} Ignoring unknown control message: {}", label, messageType); } } else { - if (logger.logsDebug()) { - logger.debug("Got invalid control message: " + data.toString()); - } + logger.debug("{} Got invalid control message: {}", label, data); close(); } } catch (ClassCastException e) { - if (logger.logsDebug()) { - logger.debug("Failed to parse control message: " + e.toString()); - } + logger.debug("{} Failed to parse control message", label, e); close(); } } private void onConnectionShutdown(String reason) { - if (logger.logsDebug()) { - logger.debug("Connection shutdown command received. Shutting down..."); - } + logger.debug("{} Connection shutdown command received. Shutting down...", label); delegate.onKill(reason); close(); } @@ -224,21 +200,15 @@ private void onHandshake(Map handshake) { } private void onConnectionReady(long timestamp, String sessionId) { - if (logger.logsDebug()) { - logger.debug("realtime connection established"); - } + logger.debug("{} Realtime connection established", label); state = State.REALTIME_CONNECTED; delegate.onReady(timestamp, sessionId); } private void onReset(String host) { - if (logger.logsDebug()) { - logger.debug( - "Got a reset; killing connection to " - + this.hostInfo.getHost() - + "; Updating internalHost to " - + host); - } + logger.debug( + "{} Got a reset; killing connection to {}; Updating internalHost to {}", + label, hostInfo.getHost(), host); delegate.onCacheHost(host); // Explicitly close the connection with SERVER_RESET so calling code knows to reconnect @@ -248,12 +218,12 @@ private void onReset(String host) { private void sendData(Map data, boolean isSensitive) { if (state != State.REALTIME_CONNECTED) { - logger.debug("Tried to send on an unconnected connection"); + logger.debug("{} Tried to send on an unconnected connection", label); } else { if (isSensitive) { - logger.debug("Sending data (contents hidden)"); + logger.debug("{} Sending data (contents hidden)", label); } else { - logger.debug("Sending data: %s", data); + logger.debug("{} Sending data: {}", label, data); } conn.send(data); } diff --git a/src/main/java/com/google/firebase/database/connection/ConnectionContext.java b/src/main/java/com/google/firebase/database/connection/ConnectionContext.java index 03b4b45f4..574bc1df9 100644 --- a/src/main/java/com/google/firebase/database/connection/ConnectionContext.java +++ b/src/main/java/com/google/firebase/database/connection/ConnectionContext.java @@ -16,8 +16,6 @@ package com.google.firebase.database.connection; -import com.google.firebase.database.logging.Logger; - import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; @@ -25,21 +23,18 @@ public class ConnectionContext { private final ScheduledExecutorService executorService; private final ConnectionAuthTokenProvider authTokenProvider; - private final Logger logger; private final boolean persistenceEnabled; private final String clientSdkVersion; private final String userAgent; private final ThreadFactory threadFactory; public ConnectionContext( - Logger logger, ConnectionAuthTokenProvider authTokenProvider, ScheduledExecutorService executorService, boolean persistenceEnabled, String clientSdkVersion, String userAgent, ThreadFactory threadFactory) { - this.logger = logger; this.authTokenProvider = authTokenProvider; this.executorService = executorService; this.persistenceEnabled = persistenceEnabled; @@ -48,10 +43,6 @@ public ConnectionContext( this.threadFactory = threadFactory; } - public Logger getLogger() { - return this.logger; - } - public ConnectionAuthTokenProvider getAuthTokenProvider() { return this.authTokenProvider; } diff --git a/src/main/java/com/google/firebase/database/connection/NettyWebSocketClient.java b/src/main/java/com/google/firebase/database/connection/NettyWebSocketClient.java index 40d172722..9769b65a8 100644 --- a/src/main/java/com/google/firebase/database/connection/NettyWebSocketClient.java +++ b/src/main/java/com/google/firebase/database/connection/NettyWebSocketClient.java @@ -5,8 +5,7 @@ import static com.google.common.base.Preconditions.checkState; import com.google.common.base.Strings; -import com.google.firebase.internal.GaeThreadFactory; -import com.google.firebase.internal.RevivingScheduledExecutor; +import com.google.firebase.internal.FirebaseScheduledExecutor; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -67,8 +66,8 @@ class NettyWebSocketClient implements WebsocketConnection.WSClient { this.uri = checkNotNull(uri, "uri must not be null"); this.eventHandler = checkNotNull(eventHandler, "event handler must not be null"); this.channelHandler = new WebSocketClientHandler(uri, userAgent, eventHandler); - this.executorService = new RevivingScheduledExecutor( - threadFactory, "firebase-websocket-worker", GaeThreadFactory.isAvailable()); + this.executorService = new FirebaseScheduledExecutor(threadFactory, + "firebase-websocket-worker"); this.group = new NioEventLoopGroup(1, this.executorService); } @@ -104,7 +103,7 @@ protected void initChannel(SocketChannel ch) { channelFuture.addListener( new ChannelFutureListener() { @Override - public void operationComplete(ChannelFuture future) throws Exception { + public void operationComplete(ChannelFuture future) { if (!future.isSuccess()) { eventHandler.onError(future.cause()); } @@ -174,7 +173,7 @@ public void channelInactive(ChannelHandlerContext context) { } @Override - public void channelRead0(ChannelHandlerContext context, Object message) throws Exception { + public void channelRead0(ChannelHandlerContext context, Object message) { Channel channel = context.channel(); if (message instanceof FullHttpResponse) { checkState(!handshaker.isHandshakeComplete()); diff --git a/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java b/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java index 5f2f71303..fc79b4cfe 100644 --- a/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java +++ b/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java @@ -19,7 +19,6 @@ import static com.google.firebase.database.connection.ConnectionUtils.hardAssert; import com.google.firebase.database.connection.util.RetryHelper; -import com.google.firebase.database.logging.LogWrapper; import com.google.firebase.database.util.GAuthToken; import java.util.ArrayList; @@ -33,6 +32,8 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class PersistentConnectionImpl implements Connection.Delegate, PersistentConnection { @@ -91,6 +92,9 @@ public class PersistentConnectionImpl implements Connection.Delegate, Persistent private static final String SERVER_KILL_INTERRUPT_REASON = "server_kill"; private static final String IDLE_INTERRUPT_REASON = "connection_idle"; private static final String TOKEN_REFRESH_INTERRUPT_REASON = "token_refresh"; + + private static final Logger logger = LoggerFactory.getLogger(PersistentConnection.class); + private static long connectionIds = 0; private final Delegate delegate; @@ -99,8 +103,8 @@ public class PersistentConnectionImpl implements Connection.Delegate, Persistent private final ConnectionFactory connFactory; private final ConnectionAuthTokenProvider authTokenProvider; private final ScheduledExecutorService executorService; - private final LogWrapper logger; private final RetryHelper retryHelper; + private final String label; private String cachedHost; private HashSet interruptReasons = new HashSet<>(); @@ -143,7 +147,7 @@ public PersistentConnectionImpl(ConnectionContext context, HostInfo info, Delega this.outstandingPuts = new HashMap<>(); this.onDisconnectRequestQueue = new ArrayList<>(); this.retryHelper = - new RetryHelper.Builder(this.executorService, context.getLogger(), RetryHelper.class) + new RetryHelper.Builder(this.executorService, RetryHelper.class) .withMinDelayAfterFailure(1000) .withRetryExponent(1.3) .withMaxDelay(30 * 1000) @@ -151,7 +155,7 @@ public PersistentConnectionImpl(ConnectionContext context, HostInfo info, Delega .build(); long connId = connectionIds++; - this.logger = new LogWrapper(context.getLogger(), PersistentConnection.class, "pc_" + connId); + this.label = "[pc_" + connId + "]"; this.lastSessionId = null; doIdleCheck(); } @@ -159,9 +163,7 @@ public PersistentConnectionImpl(ConnectionContext context, HostInfo info, Delega // Connection.Delegate methods @Override public void onReady(long timestamp, String sessionId) { - if (logger.logsDebug()) { - logger.debug("onReady"); - } + logger.debug("{} onReady", label); lastConnectionEstablishedTime = System.currentTimeMillis(); handleTimestamp(timestamp); @@ -188,16 +190,12 @@ public void listen( Long tag, RequestResultCallback listener) { ListenQuerySpec query = new ListenQuerySpec(path, queryParams); - if (logger.logsDebug()) { - logger.debug("Listening on " + query); - } + logger.debug("{} Listening on {}", label, query); // TODO: Fix this somehow? //hardAssert(query.isDefault() || !query.loadsAllData(), "listen() called for non-default but " // + "complete query"); hardAssert(!listens.containsKey(query), "listen() called twice for same QuerySpec."); - if (logger.logsDebug()) { - logger.debug("Adding listen query: " + query); - } + logger.debug("{} Adding listen query: {}", label, query); OutstandingListen outstandingListen = new OutstandingListen(listener, query, tag, currentHashFn); listens.put(query, outstandingListen); @@ -277,23 +275,19 @@ public void onDataMessage(Map message) { Map body = (Map) message.get(SERVER_ASYNC_PAYLOAD); onDataPush(action, body); } else { - if (logger.logsDebug()) { - logger.debug("Ignoring unknown message: " + message); - } + logger.debug("{} Ignoring unknown message: {}", label, message); } } @Override public void onDisconnect(Connection.DisconnectReason reason) { - if (logger.logsDebug()) { - logger.debug("Got on disconnect due to " + reason.name()); - } + logger.debug("{} Got on disconnect due to {}", label, reason.name()); this.connectionState = ConnectionState.Disconnected; this.realtime = null; this.hasOnDisconnects = false; requestCBHash.clear(); if (inactivityTimer != null) { - logger.debug("cancelling idle time checker"); + logger.debug("{} Cancelling idle time checker", label); inactivityTimer.cancel(false); inactivityTimer = null; } @@ -319,21 +313,16 @@ public void onDisconnect(Connection.DisconnectReason reason) { @Override public void onKill(String reason) { - if (logger.logsDebug()) { - logger.debug( - "Firebase Database connection was forcefully killed by the server. Will not attempt " - + "reconnect. Reason: " - + reason); - } + logger.debug( + "{} Firebase Database connection was forcefully killed by the server. Will not attempt " + + "reconnect. Reason: {}", label, reason); interrupt(SERVER_KILL_INTERRUPT_REASON); } @Override public void unlisten(List path, Map queryParams) { ListenQuerySpec query = new ListenQuerySpec(path, queryParams); - if (logger.logsDebug()) { - logger.debug("unlistening on " + query); - } + logger.debug("{} Unlistening on {}", label, query); // TODO: fix this by understanding query params? //Utilities.hardAssert(query.isDefault() || !query.loadsAllData(), @@ -395,9 +384,7 @@ public void onDisconnectCancel(List path, RequestResultCallback onComple @Override public void interrupt(String reason) { - if (logger.logsDebug()) { - logger.debug("Connection interrupted for: " + reason); - } + logger.debug("{} Connection interrupted for: {}", label, reason); interruptReasons.add(reason); if (realtime != null) { @@ -414,10 +401,7 @@ public void interrupt(String reason) { @Override public void resume(String reason) { - if (logger.logsDebug()) { - logger.debug("Connection no longer interrupted for: " + reason); - } - + logger.debug("{} Connection no longer interrupted for: {}", label, reason); interruptReasons.remove(reason); if (shouldReconnect() && connectionState == ConnectionState.Disconnected) { @@ -444,7 +428,7 @@ public void refreshAuthToken() { // we close the connection to make sure any writes/listens are queued until the connection // is reauthed with the current token after reconnecting. Note that this will trigger // onDisconnects which isn't ideal. - logger.debug("Auth token refresh requested"); + logger.debug("{} Auth token refresh requested", label); // By using interrupt instead of closing the connection we make sure there are no race // conditions with other fetch token attempts (interrupt/resume is expected to handle those @@ -455,7 +439,7 @@ public void refreshAuthToken() { @Override public void refreshAuthToken(String token) { - logger.debug("Auth token refreshed."); + logger.debug("{} Auth token refreshed.", label); this.authToken = token; if (connected()) { if (token != null) { @@ -473,13 +457,13 @@ private void tryScheduleReconnect() { "Not in disconnected state: %s", this.connectionState); final boolean forceRefresh = this.forceAuthTokenRefresh; - logger.debug("Scheduling connection attempt"); + logger.debug("{} Scheduling connection attempt", label); this.forceAuthTokenRefresh = false; retryHelper.retry( new Runnable() { @Override public void run() { - logger.debug("Trying to fetch auth token"); + logger.debug("{} Trying to fetch auth token", label); hardAssert( connectionState == ConnectionState.Disconnected, "Not in disconnected state: %s", @@ -496,7 +480,7 @@ public void onSuccess(String token) { // Someone could have interrupted us while fetching the token, // marking the connection as Disconnected if (connectionState == ConnectionState.GettingToken) { - logger.debug("Successfully fetched token, opening connection"); + logger.debug("{} Successfully fetched token, opening connection", label); openNetworkConnection(token); } else { hardAssert( @@ -504,13 +488,13 @@ public void onSuccess(String token) { "Expected connection state disconnected, but was %s", connectionState); logger.debug( - "Not opening connection after token refresh, " - + "because connection was set to disconnected"); + "{} Not opening connection after token refresh, because connection " + + "was set to disconnected", label); } } else { logger.debug( - "Ignoring getToken result, because this was not the " - + "latest attempt."); + "{} Ignoring getToken result, because this was not the " + + "latest attempt.", label); } } @@ -518,12 +502,12 @@ public void onSuccess(String token) { public void onError(String error) { if (thisGetTokenAttempt == currentGetTokenAttempt) { connectionState = ConnectionState.Disconnected; - logger.debug("Error fetching token: " + error); + logger.debug("{} Error fetching token: {}", label, error); tryScheduleReconnect(); } else { logger.debug( - "Ignoring getToken error, because this was not the " - + "latest attempt."); + "{} Ignoring getToken error, because this was not the " + + "latest attempt.", label); } } }); @@ -609,14 +593,10 @@ private void sendUnlisten(OutstandingListen listen) { } private OutstandingListen removeListen(ListenQuerySpec query) { - if (logger.logsDebug()) { - logger.debug("removing query " + query); - } + logger.debug("{} removing query {}", label, query); if (!listens.containsKey(query)) { - if (logger.logsDebug()) { - logger.debug( - "Trying to remove listener for QuerySpec " + query + " but no listener exists."); - } + logger.debug( + "{} Trying to remove listener for QuerySpec {} but no listener exists.", label, query); return null; } else { OutstandingListen oldListen = listens.get(query); @@ -627,9 +607,7 @@ private OutstandingListen removeListen(ListenQuerySpec query) { } private Collection removeListens(List path) { - if (logger.logsDebug()) { - logger.debug("removing all listens at path " + path); - } + logger.debug("{} Removing all listens at path {}", label, path); List removedListens = new ArrayList<>(); for (Map.Entry entry : listens.entrySet()) { ListenQuerySpec query = entry.getKey(); @@ -649,9 +627,7 @@ private Collection removeListens(List path) { } private void onDataPush(String action, Map body) { - if (logger.logsDebug()) { - logger.debug("handleServerMessage: " + action + " " + body); - } + logger.debug("{} handleServerMessage: {} {}", label, action, body); if (action.equals(SERVER_ASYNC_DATA_UPDATE) || action.equals(SERVER_ASYNC_DATA_MERGE)) { boolean isMerge = action.equals(SERVER_ASYNC_DATA_MERGE); @@ -660,9 +636,7 @@ private void onDataPush(String action, Map body) { Long tagNumber = ConnectionUtils.longFromObject(body.get(SERVER_DATA_TAG)); // ignore empty merges if (isMerge && (payloadData instanceof Map) && ((Map) payloadData).size() == 0) { - if (logger.logsDebug()) { - logger.debug("ignoring empty merge for path " + pathString); - } + logger.debug("{} Ignoring empty merge for path {}", label, pathString); } else { List path = ConnectionUtils.stringToPath(pathString); delegate.onDataUpdate(path, payloadData, isMerge, tagNumber); @@ -684,9 +658,7 @@ private void onDataPush(String action, Map body) { rangeMerges.add(new RangeMerge(start, end, update)); } if (rangeMerges.isEmpty()) { - if (logger.logsDebug()) { - logger.debug("Ignoring empty range merge for path " + pathString); - } + logger.debug("{} Ignoring empty range merge for path {}", label, pathString); } else { this.delegate.onRangeMergeUpdate(path, rangeMerges, tag); } @@ -701,9 +673,7 @@ private void onDataPush(String action, Map body) { } else if (action.equals(SERVER_ASYNC_SECURITY_DEBUG)) { onSecurityDebugPacket(body); } else { - if (logger.logsDebug()) { - logger.debug("Unrecognized action from server: " + action); - } + logger.debug("{} Unrecognized action from server: {}", label, action); } } @@ -723,7 +693,7 @@ private void onAuthRevoked(String errorCode, String errorMessage) { // This might be for an earlier token than we just recently sent. But since we need to close // the connection anyways, we can set it to null here and we will refresh the token later // on reconnect. - logger.debug("Auth token revoked: " + errorCode + " (" + errorMessage + ")"); + logger.debug("{} Auth token revoked: {} ({})", label, errorCode, errorMessage); this.authToken = null; this.forceAuthTokenRefresh = true; this.delegate.onAuthStatus(false); @@ -733,7 +703,7 @@ private void onAuthRevoked(String errorCode, String errorMessage) { private void onSecurityDebugPacket(Map message) { // TODO: implement on iOS too - logger.info((String) message.get("msg")); + logger.info("{} {}", label, message.get("msg")); } private void upgradeAuth() { @@ -766,7 +736,7 @@ public void onResponse(Map response) { forceAuthTokenRefresh = true; delegate.onAuthStatus(false); String reason = (String) response.get(SERVER_RESPONSE_DATA); - logger.debug("Authentication failed: " + status + " (" + reason + ")"); + logger.debug("{} Authentication failed: {} ({})", label, status, reason); realtime.close(); if (status.equals("invalid_token") || status.equals("permission_denied")) { @@ -778,11 +748,11 @@ public void onResponse(Map response) { // Set a long reconnect delay because recovery is unlikely. retryHelper.setMaxDelay(); logger.warn( - "Provided authentication credentials are invalid. This " + "{} Provided authentication credentials are invalid. This " + "usually indicates your FirebaseApp instance was not initialized " + "correctly. Make sure your database URL is correct and that your " + "service account is for the correct project and is authorized to " - + "access it."); + + "access it.", label); } } } @@ -814,9 +784,7 @@ private void sendUnauth() { } private void restoreAuth() { - if (logger.logsDebug()) { - logger.debug("calling restore state"); - } + logger.debug("{} Calling restore state", label); hardAssert( this.connectionState == ConnectionState.Connecting, @@ -824,15 +792,11 @@ private void restoreAuth() { this.connectionState); if (authToken == null) { - if (logger.logsDebug()) { - logger.debug("Not restoring auth because token is null."); - } + logger.debug("{} Not restoring auth because token is null.", label); this.connectionState = ConnectionState.Connected; restoreState(); } else { - if (logger.logsDebug()) { - logger.debug("Restoring auth."); - } + logger.debug("{} Restoring auth.", label); this.connectionState = ConnectionState.Authenticating; sendAuthAndRestoreState(); } @@ -845,19 +809,13 @@ private void restoreState() { this.connectionState); // Restore listens - if (logger.logsDebug()) { - logger.debug("Restoring outstanding listens"); - } + logger.debug("{} Restoring outstanding listens", label); for (OutstandingListen listen : listens.values()) { - if (logger.logsDebug()) { - logger.debug("Restoring listen " + listen.getQuery()); - } + logger.debug("{} Restoring listen {}", label, listen.getQuery()); sendListen(listen); } - if (logger.logsDebug()) { - logger.debug("Restoring writes."); - } + logger.debug("{} Restoring writes.", label); // Restore puts ArrayList outstanding = new ArrayList<>(outstandingPuts.keySet()); // Make sure puts are restored in order @@ -878,9 +836,7 @@ private void restoreState() { } private void handleTimestamp(long timestamp) { - if (logger.logsDebug()) { - logger.debug("handling timestamp"); - } + logger.debug("{} Handling timestamp", label); long timestampDelta = timestamp - System.currentTimeMillis(); Map updates = new HashMap<>(); updates.put(Constants.DOT_INFO_SERVERTIME_OFFSET, timestampDelta); @@ -930,9 +886,7 @@ assert canSendWrites() new ConnectionRequestCallback() { @Override public void onResponse(Map response) { - if (logger.logsDebug()) { - logger.debug(action + " response: " + response); - } + logger.debug("{} {} response: {}", label, action, response); OutstandingPut currentPut = outstandingPuts.get(putId); if (currentPut == put) { @@ -948,10 +902,8 @@ public void onResponse(Map response) { } } } else { - if (logger.logsDebug()) { - logger.debug( - "Ignoring on complete for put " + putId + " because it was removed already."); - } + logger.debug("{} Ignoring on complete for put {} because it was removed already.", + label, putId); } doIdleCheck(); } @@ -1032,17 +984,13 @@ public void onResponse(Map response) { String status = (String) response.get(REQUEST_STATUS); if (!status.equals("ok")) { String errorMessage = (String) response.get(SERVER_DATA_UPDATE_BODY); - if (logger.logsDebug()) { - logger.debug( - "Failed to send stats: " + status + " (message: " + errorMessage + ")"); - } + logger.debug( + "{} Failed to send stats: {} (message: {})", label, stats, errorMessage); } } }); } else { - if (logger.logsDebug()) { - logger.debug("Not sending stats because stats are empty"); - } + logger.debug("{} Not sending stats because stats are empty", label); } } @@ -1051,11 +999,9 @@ private void warnOnListenerWarnings(List warnings, ListenQuerySpec query if (warnings.contains("no_index")) { String indexSpec = "\".indexOn\": \"" + query.queryParams.get("i") + '\"'; logger.warn( - "Using an unspecified index. Consider adding '" - + indexSpec - + "' at " - + ConnectionUtils.pathToString(query.path) - + " to your security and Firebase Database rules for better performance"); + "{} Using an unspecified index. Consider adding '{}' at {} to your security and " + + "Firebase Database rules for better performance", + label, indexSpec, ConnectionUtils.pathToString(query.path)); } } @@ -1064,9 +1010,7 @@ private void sendConnectStats() { assert !this.context.isPersistenceEnabled() : "Stats for persistence on JVM missing (persistence not yet supported)"; stats.put("sdk.admin_java." + context.getClientSdkVersion().replace('.', '-'), 1); - if (logger.logsDebug()) { - logger.debug("Sending first connection stats"); - } + logger.debug("{} Sending first connection stats", label); sendStats(stats); } diff --git a/src/main/java/com/google/firebase/database/connection/WebsocketConnection.java b/src/main/java/com/google/firebase/database/connection/WebsocketConnection.java index 9b28a19dd..3e29db07a 100644 --- a/src/main/java/com/google/firebase/database/connection/WebsocketConnection.java +++ b/src/main/java/com/google/firebase/database/connection/WebsocketConnection.java @@ -20,7 +20,6 @@ import static com.google.common.base.Preconditions.checkState; import com.google.common.collect.ImmutableList; -import com.google.firebase.database.logging.LogWrapper; import com.google.firebase.database.util.JsonMapper; import java.io.EOFException; @@ -33,6 +32,8 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Represents a WebSocket connection to the Firebase Realtime Database. This abstraction acts as @@ -47,11 +48,12 @@ class WebsocketConnection { private static final long CONNECT_TIMEOUT_MS = 30 * 1000; // 30 seconds private static final int MAX_FRAME_SIZE = 16384; private static final AtomicLong CONN_ID = new AtomicLong(0); + private static final Logger logger = LoggerFactory.getLogger(WebsocketConnection.class); private final ScheduledExecutorService executorService; - private final LogWrapper logger; private final WSClient conn; private final Delegate delegate; + private final String label; private StringList buffer; private boolean everConnected = false; @@ -75,8 +77,7 @@ class WebsocketConnection { WSClientFactory clientFactory) { this.executorService = connectionContext.getExecutorService(); this.delegate = delegate; - this.logger = new LogWrapper(connectionContext.getLogger(), WebsocketConnection.class, - "ws_" + CONN_ID.getAndIncrement()); + this.label = "[ws_" + CONN_ID.getAndIncrement() + "]"; this.conn = clientFactory.newClient(new WSClientHandlerImpl()); } @@ -99,9 +100,7 @@ void start() { } void close() { - if (logger.logsDebug()) { - logger.debug("websocket is being closed"); - } + logger.debug("{} Websocket is being closed", label); isClosed = true; conn.close(); @@ -130,7 +129,7 @@ void send(Map message) { conn.send(seg); } } catch (IOException e) { - logger.error("Failed to serialize message: " + message.toString(), e); + logger.error("{} Failed to serialize message: {}", label, message, e); closeAndNotify(); } } @@ -150,9 +149,7 @@ private List splitIntoFrames(String src, int maxFrameSize) { } private void handleNewFrameCount(int numFrames) { - if (logger.logsDebug()) { - logger.debug("HandleNewFrameCount: " + numFrames); - } + logger.debug("{} Handle new frame count: {}", label, numFrames); buffer = new StringList(numFrames); } @@ -165,15 +162,13 @@ private void appendFrame(String message) { String combined = buffer.combine(); try { Map decoded = JsonMapper.parseJson(combined); - if (logger.logsDebug()) { - logger.debug("handleIncomingFrame complete frame: " + decoded); - } + logger.debug("{} Parsed complete frame: {}", label, decoded); delegate.onMessage(decoded); } catch (IOException e) { - logger.error("Error parsing frame: " + combined, e); + logger.error("{} Error parsing frame: {}", label, combined, e); closeAndNotify(); } catch (ClassCastException e) { - logger.error("Error parsing frame (cast error): " + combined, e); + logger.error("{} Error parsing frame (cast error): {}", label, combined, e); closeAndNotify(); } } @@ -218,13 +213,10 @@ private void resetKeepAlive() { } if (keepAlive != null) { keepAlive.cancel(false); - if (logger.logsDebug()) { - logger.debug("Reset keepAlive. Remaining: " + keepAlive.getDelay(TimeUnit.MILLISECONDS)); - } + logger.debug("{} Reset keepAlive. Remaining: {}", label, + keepAlive.getDelay(TimeUnit.MILLISECONDS)); } else { - if (logger.logsDebug()) { - logger.debug("Reset keepAlive"); - } + logger.debug("{} Reset keepAlive", label); } keepAlive = executorService.schedule(nop(), KEEP_ALIVE_TIMEOUT_MS, TimeUnit.MILLISECONDS); } @@ -253,18 +245,14 @@ private void closeAndNotify() { private void onClosed() { if (!isClosed) { - if (logger.logsDebug()) { - logger.debug("closing itself"); - } + logger.debug("{} Closing itself", label); closeAndNotify(); } } private void closeIfNeverConnected() { if (!everConnected && !isClosed) { - if (logger.logsDebug()) { - logger.debug("timed out on connect"); - } + logger.debug("{} Timed out on connect", label); closeAndNotify(); } } @@ -278,9 +266,7 @@ private class WSClientHandlerImpl implements WSClientEventHandler { @Override public void onOpen() { - if (logger.logsDebug()) { - logger.debug("websocket opened"); - } + logger.debug("{} Websocket opened", label); executorService.execute(new Runnable() { @Override public void run() { @@ -293,9 +279,7 @@ public void run() { @Override public void onMessage(final String message) { - if (logger.logsDebug()) { - logger.debug("ws message: " + message); - } + logger.debug("{} WS message: {}", label, message); executorService.execute(new Runnable() { @Override public void run() { @@ -306,9 +290,7 @@ public void run() { @Override public void onClose() { - if (logger.logsDebug()) { - logger.debug("closed"); - } + logger.debug("{} Closed", label); if (!isClosed) { // If the connection tear down was initiated by the higher-layer, isClosed will already // be true. Nothing more to do in that case. @@ -325,9 +307,9 @@ public void run() { @Override public void onError(final Throwable e) { if (e instanceof EOFException || e.getCause() instanceof EOFException) { - logger.debug("WebSocket reached EOF", e); + logger.debug("{} WebSocket reached EOF", label, e); } else { - logger.error("WebSocket error", e); + logger.error("{} WebSocket error", label, e); } executorService.execute( new Runnable() { diff --git a/src/main/java/com/google/firebase/database/connection/util/RetryHelper.java b/src/main/java/com/google/firebase/database/connection/util/RetryHelper.java index 2563a30ca..f8f6bb77a 100644 --- a/src/main/java/com/google/firebase/database/connection/util/RetryHelper.java +++ b/src/main/java/com/google/firebase/database/connection/util/RetryHelper.java @@ -16,18 +16,18 @@ package com.google.firebase.database.connection.util; -import com.google.firebase.database.logging.LogWrapper; -import com.google.firebase.database.logging.Logger; - import java.util.Random; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class RetryHelper { + private static final Logger logger = LoggerFactory.getLogger(RetryHelper.class); + private final ScheduledExecutorService executorService; - private final LogWrapper logger; /** The minimum delay for a retry in ms. */ private final long minRetryDelayAfterFailure; /** The maximum retry delay in ms. */ @@ -49,13 +49,11 @@ public class RetryHelper { private RetryHelper( ScheduledExecutorService executorService, - LogWrapper logger, long minRetryDelayAfterFailure, long maxRetryDelay, double retryExponent, double jitterFactor) { this.executorService = executorService; - this.logger = logger; this.minRetryDelayAfterFailure = minRetryDelayAfterFailure; this.maxRetryDelay = maxRetryDelay; this.retryExponent = retryExponent; @@ -84,7 +82,7 @@ public void retry(final Runnable runnable) { + (jitterFactor * currentRetryDelay * random.nextDouble())); } this.lastWasSuccess = false; - logger.debug("Scheduling retry in %dms", delay); + logger.debug("Scheduling retry in {}ms", delay); Runnable wrapped = new Runnable() { @Override @@ -119,15 +117,13 @@ public void cancel() { public static class Builder { private final ScheduledExecutorService service; - private final LogWrapper logger; private long minRetryDelayAfterFailure = 1000; private double jitterFactor = 0.5; private long retryMaxDelay = 30 * 1000; private double retryExponent = 1.3; - public Builder(ScheduledExecutorService service, Logger logger, Class tag) { + public Builder(ScheduledExecutorService service, Class tag) { this.service = service; - this.logger = new LogWrapper(logger, tag); } public Builder withMinDelayAfterFailure(long delay) { @@ -156,7 +152,6 @@ public Builder withJitterFactor(double random) { public RetryHelper build() { return new RetryHelper( this.service, - this.logger, this.minRetryDelayAfterFailure, this.retryMaxDelay, this.retryExponent, diff --git a/src/main/java/com/google/firebase/database/core/Context.java b/src/main/java/com/google/firebase/database/core/Context.java index 52c07b8c6..da68861cc 100644 --- a/src/main/java/com/google/firebase/database/core/Context.java +++ b/src/main/java/com/google/firebase/database/core/Context.java @@ -26,29 +26,24 @@ import com.google.firebase.database.connection.PersistentConnection; import com.google.firebase.database.core.persistence.NoopPersistenceManager; import com.google.firebase.database.core.persistence.PersistenceManager; -import com.google.firebase.database.logging.LogWrapper; -import com.google.firebase.database.logging.Logger; import com.google.firebase.database.utilities.DefaultRunLoop; -import java.util.List; import java.util.concurrent.ScheduledExecutorService; public class Context { private static final long DEFAULT_CACHE_SIZE = 10 * 1024 * 1024; - protected Logger logger; - protected EventTarget eventTarget; - protected AuthTokenProvider authTokenProvider; - protected RunLoop runLoop; - protected String persistenceKey; - protected List loggedComponents; - protected String userAgent; - protected Logger.Level logLevel = Logger.Level.INFO; - protected boolean persistenceEnabled; - protected long cacheSize = DEFAULT_CACHE_SIZE; - protected FirebaseApp firebaseApp; - private PersistenceManager forcedPersistenceManager; + FirebaseApp firebaseApp; + + EventTarget eventTarget; + AuthTokenProvider authTokenProvider; + RunLoop runLoop; + String persistenceKey; + boolean persistenceEnabled; + long cacheSize = DEFAULT_CACHE_SIZE; + + private String userAgent; private boolean frozen = false; private boolean stopped = false; @@ -78,19 +73,11 @@ public void onError(String error) { private Platform getPlatform() { if (platform == null) { - if (GaePlatform.isActive()) { - platform = new GaePlatform(firebaseApp); - } else { - platform = new JvmPlatform(firebaseApp); - } + platform = new JvmPlatform(firebaseApp); } return platform; } - public boolean isFrozen() { - return frozen; - } - public boolean isStopped() { return stopped; } @@ -110,8 +97,6 @@ public void requireStarted() { } private void initServices() { - // Do the logger first, so that other components can get a LogWrapper - ensureLogger(); // Cache platform getPlatform(); ensureUserAgent(); @@ -137,28 +122,15 @@ void stop() { } } - protected void assertUnfrozen() { - if (isFrozen()) { + void assertUnfrozen() { + if (frozen) { throw new DatabaseException( "Modifications to DatabaseConfig objects must occur before they are in use"); } } - public LogWrapper getLogger(String component) { - return new LogWrapper(logger, component, null); - } - - public LogWrapper getLogger(Class component) { - return new LogWrapper(logger, component); - } - - public LogWrapper getLogger(Class component, String prefix) { - return new LogWrapper(logger, component, prefix); - } - public ConnectionContext getConnectionContext() { return new ConnectionContext( - this.logger, wrapAuthTokenProvider(this.getAuthTokenProvider()), this.getExecutorService(), this.isPersistenceEnabled(), @@ -168,10 +140,6 @@ public ConnectionContext getConnectionContext() { } PersistenceManager getPersistenceManager(String firebaseId) { - // TODO[persistence]: Create this once and store it. - if (forcedPersistenceManager != null) { - return forcedPersistenceManager; - } if (this.persistenceEnabled) { PersistenceManager cache = platform.createPersistenceManager(this, firebaseId); if (cache == null) { @@ -193,11 +161,6 @@ public long getPersistenceCacheSizeBytes() { return this.cacheSize; } - // For testing - void forcePersistenceManager(PersistenceManager persistenceManager) { - this.forcedPersistenceManager = persistenceManager; - } - public EventTarget getEventTarget() { return eventTarget; } @@ -210,14 +173,6 @@ public String getUserAgent() { return userAgent; } - public String getPlatformVersion() { - return getPlatform().getPlatformVersion(); - } - - public String getSessionPersistenceKey() { - return this.persistenceKey; - } - public AuthTokenProvider getAuthTokenProvider() { return this.authTokenProvider; } @@ -237,12 +192,6 @@ private ScheduledExecutorService getExecutorService() { return ((DefaultRunLoop) loop).getExecutorService(); } - private void ensureLogger() { - if (logger == null) { - logger = getPlatform().newLogger(this, logLevel, loggedComponents); - } - } - private void ensureRunLoop() { if (runLoop == null) { runLoop = platform.newRunLoop(this); diff --git a/src/main/java/com/google/firebase/database/core/DatabaseConfig.java b/src/main/java/com/google/firebase/database/core/DatabaseConfig.java index 491098d6a..844f59266 100644 --- a/src/main/java/com/google/firebase/database/core/DatabaseConfig.java +++ b/src/main/java/com/google/firebase/database/core/DatabaseConfig.java @@ -18,9 +18,6 @@ import com.google.firebase.FirebaseApp; import com.google.firebase.database.DatabaseException; -import com.google.firebase.database.Logger; - -import java.util.List; /** * TODO: Since this is no longer public, we should merge it with Context and clean all @@ -29,21 +26,6 @@ */ public class DatabaseConfig extends Context { - // TODO: Remove this from the public API since we currently can't pass logging - // across AIDL interface. - - /** - * If you would like to provide a custom log target, pass an object that implements the {@link - * com.google.firebase.database.Logger Logger} interface. - * - * @hide - * @param logger The custom logger that will be called with all log messages - */ - public synchronized void setLogger(com.google.firebase.database.logging.Logger logger) { - assertUnfrozen(); - this.logger = logger; - } - /** * In the default setup, the Firebase Database library will create a thread to handle all * callbacks. On Android, it will attempt to use the main debugComponents) { - assertUnfrozen(); - setLogLevel(Logger.Level.DEBUG); - loggedComponents = debugComponents; - } - public void setRunLoop(RunLoop runLoop) { this.runLoop = runLoop; } diff --git a/src/main/java/com/google/firebase/database/core/GaePlatform.java b/src/main/java/com/google/firebase/database/core/GaePlatform.java deleted file mode 100644 index b7698f3de..000000000 --- a/src/main/java/com/google/firebase/database/core/GaePlatform.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.database.core; - -import com.google.firebase.FirebaseApp; -import com.google.firebase.ImplFirebaseTrampolines; -import com.google.firebase.database.FirebaseDatabase; -import com.google.firebase.database.connection.ConnectionContext; -import com.google.firebase.database.connection.HostInfo; -import com.google.firebase.database.connection.PersistentConnection; -import com.google.firebase.database.connection.PersistentConnectionImpl; -import com.google.firebase.database.core.persistence.PersistenceManager; -import com.google.firebase.database.logging.DefaultLogger; -import com.google.firebase.database.logging.LogWrapper; -import com.google.firebase.database.logging.Logger; -import com.google.firebase.database.utilities.DefaultRunLoop; -import com.google.firebase.internal.GaeThreadFactory; -import com.google.firebase.internal.RevivingScheduledExecutor; - -import java.util.List; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; - -/** - * Represents a Google AppEngine platform. - * - *

    This class is not thread-safe. - */ -class GaePlatform implements Platform { - - private static final String PROCESS_PLATFORM = "AppEngine"; - private final FirebaseApp firebaseApp; - - public GaePlatform(FirebaseApp firebaseApp) { - this.firebaseApp = firebaseApp; - } - - public static boolean isActive() { - return GaeThreadFactory.isAvailable(); - } - - @Override - public Logger newLogger(Context ctx, Logger.Level level, List components) { - return new DefaultLogger(level, components); - } - - private ThreadFactory getGaeThreadFactory() { - return ImplFirebaseTrampolines.getThreadFactory(firebaseApp); - } - - @Override - public EventTarget newEventTarget(Context ctx) { - RevivingScheduledExecutor eventExecutor = - new RevivingScheduledExecutor(getGaeThreadFactory(), "FirebaseDatabaseEventTarget", true); - return new ThreadPoolEventTarget(eventExecutor); - } - - @Override - public RunLoop newRunLoop(final Context context) { - final LogWrapper logger = context.getLogger(RunLoop.class); - return new DefaultRunLoop(getGaeThreadFactory(), /* periodicRestart= */ true, context) { - @Override - public void handleException(Throwable e) { - logger.error(DefaultRunLoop.messageForException(e), e); - } - }; - } - - @Override - public AuthTokenProvider newAuthTokenProvider(ScheduledExecutorService executorService) { - return new JvmAuthTokenProvider(this.firebaseApp, executorService); - } - - @Override - public PersistentConnection newPersistentConnection( - Context context, - ConnectionContext connectionContext, - HostInfo info, - PersistentConnection.Delegate delegate) { - return new PersistentConnectionImpl(context.getConnectionContext(), info, delegate); - } - - @Override - public String getUserAgent(Context ctx) { - return PROCESS_PLATFORM + "/" + DEVICE; - } - - @Override - public String getPlatformVersion() { - return "gae-" + FirebaseDatabase.getSdkVersion(); - } - - @Override - public PersistenceManager createPersistenceManager(Context ctx, String namespace) { - return null; - } - - @Override - public ThreadInitializer getThreadInitializer() { - return new ThreadInitializer() { - @Override - public void setName(Thread t, String name) { - // Unsupported by GAE - } - - @Override - public void setDaemon(Thread t, boolean isDaemon) { - // Unsupported by GAE - } - - @Override - public void setUncaughtExceptionHandler(Thread t, Thread.UncaughtExceptionHandler handler) { - // Unsupported by GAE - } - }; - } -} diff --git a/src/main/java/com/google/firebase/database/core/JvmPlatform.java b/src/main/java/com/google/firebase/database/core/JvmPlatform.java index 30a2e25df..2a226c078 100644 --- a/src/main/java/com/google/firebase/database/core/JvmPlatform.java +++ b/src/main/java/com/google/firebase/database/core/JvmPlatform.java @@ -24,14 +24,12 @@ import com.google.firebase.database.connection.PersistentConnection; import com.google.firebase.database.connection.PersistentConnectionImpl; import com.google.firebase.database.core.persistence.PersistenceManager; -import com.google.firebase.database.logging.DefaultLogger; -import com.google.firebase.database.logging.LogWrapper; -import com.google.firebase.database.logging.Logger; import com.google.firebase.database.utilities.DefaultRunLoop; -import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class JvmPlatform implements Platform { @@ -39,24 +37,19 @@ class JvmPlatform implements Platform { private final FirebaseApp firebaseApp; - public JvmPlatform(FirebaseApp firebaseApp) { + JvmPlatform(FirebaseApp firebaseApp) { this.firebaseApp = firebaseApp; } - @Override - public Logger newLogger(Context ctx, Logger.Level level, List components) { - return new DefaultLogger(level, components); - } - @Override public EventTarget newEventTarget(Context ctx) { ThreadFactory threadFactory = ImplFirebaseTrampolines.getThreadFactory(firebaseApp); - return new ThreadPoolEventTarget(threadFactory, ThreadInitializer.defaultInstance); + return new ThreadPoolEventTarget(threadFactory); } @Override public RunLoop newRunLoop(final Context context) { - final LogWrapper logger = context.getLogger(RunLoop.class); + final Logger logger = LoggerFactory.getLogger(RunLoop.class); ThreadFactory threadFactory = ImplFirebaseTrampolines.getThreadFactory(firebaseApp); return new DefaultRunLoop(threadFactory) { @Override @@ -94,9 +87,4 @@ public String getPlatformVersion() { public PersistenceManager createPersistenceManager(Context ctx, String namespace) { return null; } - - @Override - public ThreadInitializer getThreadInitializer() { - return ThreadInitializer.defaultInstance; - } } diff --git a/src/main/java/com/google/firebase/database/core/Platform.java b/src/main/java/com/google/firebase/database/core/Platform.java index 5e701c408..2d2742a62 100644 --- a/src/main/java/com/google/firebase/database/core/Platform.java +++ b/src/main/java/com/google/firebase/database/core/Platform.java @@ -20,17 +20,13 @@ import com.google.firebase.database.connection.HostInfo; import com.google.firebase.database.connection.PersistentConnection; import com.google.firebase.database.core.persistence.PersistenceManager; -import com.google.firebase.database.logging.Logger; -import java.util.List; import java.util.concurrent.ScheduledExecutorService; public interface Platform { String DEVICE = "AdminJava"; - Logger newLogger(Context ctx, Logger.Level level, List components); - EventTarget newEventTarget(Context ctx); RunLoop newRunLoop(Context ctx); @@ -48,6 +44,4 @@ PersistentConnection newPersistentConnection( String getPlatformVersion(); PersistenceManager createPersistenceManager(Context ctx, String firebaseId); - - ThreadInitializer getThreadInitializer(); } diff --git a/src/main/java/com/google/firebase/database/core/Repo.java b/src/main/java/com/google/firebase/database/core/Repo.java index add6ff3e0..98777d04d 100644 --- a/src/main/java/com/google/firebase/database/core/Repo.java +++ b/src/main/java/com/google/firebase/database/core/Repo.java @@ -38,7 +38,6 @@ import com.google.firebase.database.core.view.Event; import com.google.firebase.database.core.view.EventRaiser; import com.google.firebase.database.core.view.QuerySpec; -import com.google.firebase.database.logging.LogWrapper; import com.google.firebase.database.snapshot.ChildKey; import com.google.firebase.database.snapshot.EmptyNode; import com.google.firebase.database.snapshot.IndexedNode; @@ -54,6 +53,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Repo implements PersistentConnection.Delegate { @@ -67,14 +68,14 @@ public class Repo implements PersistentConnection.Delegate { private static final String TRANSACTION_TOO_MANY_RETRIES = "maxretries"; private static final String TRANSACTION_OVERRIDE_BY_SET = "overriddenBySet"; + + private static final Logger logger = LoggerFactory.getLogger(Repo.class); + private final RepoInfo repoInfo; private final OffsetClock serverClock = new OffsetClock(new DefaultClock(), 0); private final PersistentConnection connection; private final EventRaiser eventRaiser; private final Context ctx; - private final LogWrapper operationLogger; - private final LogWrapper transactionLogger; - private final LogWrapper dataLogger; private SnapshotHolder infoData; private SparseSnapshotTree onDisconnect; private Tree> transactionQueueTree; @@ -90,11 +91,6 @@ public class Repo implements PersistentConnection.Delegate { this.repoInfo = repoInfo; this.ctx = ctx; this.database = database; - - operationLogger = this.ctx.getLogger(Repo.class); - transactionLogger = this.ctx.getLogger(Repo.class.getName() + ".Transaction"); - dataLogger = this.ctx.getLogger(Repo.class.getName() + ".DataOperation"); - this.eventRaiser = new EventRaiser(this.ctx); HostInfo hostInfo = new HostInfo(repoInfo.host, repoInfo.namespace, repoInfo.secure); @@ -134,7 +130,7 @@ private void deferredInitialization() { new AuthTokenProvider.TokenChangeListener() { @Override public void onTokenChange(String token) { - operationLogger.debug("Auth token changed, triggering auth token refresh"); + logger.debug("Auth token changed, triggering auth token refresh"); connection.refreshAuthToken(token); } }); @@ -150,7 +146,7 @@ public void onTokenChange(String token) { transactionQueueTree = new Tree<>(); - infoSyncTree = new SyncTree(ctx, new NoopPersistenceManager(), + infoSyncTree = new SyncTree(new NoopPersistenceManager(), new SyncTree.ListenProvider() { @Override public void startListening( @@ -178,7 +174,7 @@ public void run() { public void stopListening(QuerySpec query, Tag tag) {} }); - serverSyncTree = new SyncTree(ctx, persistenceManager, + serverSyncTree = new SyncTree(persistenceManager, new SyncTree.ListenProvider() { @Override public void startListening( @@ -263,12 +259,8 @@ boolean hasListeners() { public void onDataUpdate( List pathSegments, Object message, boolean isMerge, Long optTag) { Path path = new Path(pathSegments); - if (operationLogger.logsDebug()) { - operationLogger.debug("onDataUpdate: " + path); - } - if (dataLogger.logsDebug()) { - operationLogger.debug("onDataUpdate: " + path + " " + message); - } + logger.debug("onDataUpdate: {} {}", path, message); + List events; try { @@ -306,7 +298,7 @@ public void onDataUpdate( postEvents(events); } catch (DatabaseException e) { - operationLogger.error("FIREBASE INTERNAL ERROR", e); + logger.error("Firebase internal error", e); } } @@ -316,12 +308,7 @@ public void onRangeMergeUpdate( List merges, Long tagNumber) { Path path = new Path(pathSegments); - if (operationLogger.logsDebug()) { - operationLogger.debug("onRangeMergeUpdate: " + path); - } - if (dataLogger.logsDebug()) { - operationLogger.debug("onRangeMergeUpdate: " + path + " " + merges); - } + logger.debug("onRangeMergeUpdate: {} {}", path, merges); List parsedMerges = new ArrayList<>(merges.size()); for (com.google.firebase.database.connection.RangeMerge merge : merges) { @@ -383,12 +370,7 @@ public void setValue( final Path path, Node newValueUnresolved, final DatabaseReference.CompletionListener onComplete) { - if (operationLogger.logsDebug()) { - operationLogger.debug("set: " + path); - } - if (dataLogger.logsDebug()) { - dataLogger.debug("set: " + path + " " + newValueUnresolved); - } + logger.debug("set: {} {}", path, newValueUnresolved); Map serverValues = ServerValues.generateServerValues(serverClock); Node newValue = ServerValues.resolveDeferredValueSnapshot(newValueUnresolved, serverValues); @@ -421,16 +403,9 @@ public void updateChildren( CompoundWrite updates, final DatabaseReference.CompletionListener onComplete, Map unParsedUpdates) { - if (operationLogger.logsDebug()) { - operationLogger.debug("update: " + path); - } - if (dataLogger.logsDebug()) { - dataLogger.debug("update: " + path + " " + unParsedUpdates); - } + logger.debug("update: {} {}", path, unParsedUpdates); if (updates.isEmpty()) { - if (operationLogger.logsDebug()) { - operationLogger.debug("update called with no changes. No-op"); - } + logger.debug("update called with no changes. No-op"); // dispatch on complete callOnComplete(onComplete, null, path); return; @@ -468,9 +443,7 @@ public void onRequestResult(String optErrorCode, String optErrorMessage) { } public void purgeOutstandingWrites() { - if (operationLogger.logsDebug()) { - operationLogger.debug("Purging writes"); - } + logger.debug("Purging writes"); List events = serverSyncTree.removeAllWrites(); postEvents(events); // Abort any transactions @@ -619,7 +592,7 @@ private void updateInfo(ChildKey childKey, Object value) { List events = this.infoSyncTree.applyServerOverwrite(path, node); this.postEvents(events); } catch (DatabaseException e) { - operationLogger.error("Failed to parse info update", e); + logger.error("Failed to parse info update", e); } } @@ -652,21 +625,16 @@ private void warnIfWriteFailed(String writeType, Path path, DatabaseError error) if (error != null && !(error.getCode() == DatabaseError.DATA_STALE || error.getCode() == DatabaseError.WRITE_CANCELED)) { - operationLogger.warn(writeType + " at " + path.toString() + " failed: " + error.toString()); + logger.warn(writeType + " at " + path.toString() + " failed: " + error.toString()); } } public void startTransaction(Path path, final Transaction.Handler handler, boolean applyLocally) { - if (operationLogger.logsDebug()) { - operationLogger.debug("transaction: " + path); - } - if (dataLogger.logsDebug()) { - operationLogger.debug("transaction: " + path); - } + logger.debug("transaction: {}", path); if (this.ctx.isPersistenceEnabled() && !loggedTransactionPersistenceWarning) { loggedTransactionPersistenceWarning = true; - transactionLogger.info( + logger.info( "runTransaction() usage detected while persistence is enabled. Please be aware that " + "transactions *will not* be persisted across database restarts. See " + "https://www.firebase.com/docs/android/guide/offline-capabilities.html" @@ -1142,11 +1110,7 @@ public void visitTree(Tree> tree) { private Path abortTransactions(Path path, final int reason) { Path affectedPath = getAncestorTransactionNode(path).getPath(); - - if (transactionLogger.logsDebug()) { - operationLogger.debug( - "Aborting transactions for path: " + path + ". Affected: " + affectedPath); - } + logger.debug("Aborting transactions for path: {}. Affected: {}", path, affectedPath); Tree> transactionNode = transactionQueueTree.subTree(path); transactionNode.forEachAncestor( @@ -1247,7 +1211,7 @@ private void runTransactionOnComplete(Transaction.Handler handler, DatabaseError try { handler.onComplete(error, committed, snapshot); } catch (Exception e) { - operationLogger.error("Exception in transaction onComplete callback", e); + logger.error("Exception in transaction onComplete callback", e); } } diff --git a/src/main/java/com/google/firebase/database/core/SyncTree.java b/src/main/java/com/google/firebase/database/core/SyncTree.java index 17fcad445..f7da11750 100644 --- a/src/main/java/com/google/firebase/database/core/SyncTree.java +++ b/src/main/java/com/google/firebase/database/core/SyncTree.java @@ -37,7 +37,6 @@ import com.google.firebase.database.core.view.Event; import com.google.firebase.database.core.view.QuerySpec; import com.google.firebase.database.core.view.View; -import com.google.firebase.database.logging.LogWrapper; import com.google.firebase.database.snapshot.ChildKey; import com.google.firebase.database.snapshot.CompoundHash; import com.google.firebase.database.snapshot.EmptyNode; @@ -57,6 +56,8 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * SyncTree is the central class for managing event callback registration, data caching, views @@ -80,6 +81,9 @@ public class SyncTree { // Size after which we start including the compound hash private static final long SIZE_THRESHOLD_FOR_COMPOUND_HASH = 1024; + + private static final Logger logger = LoggerFactory.getLogger(SyncTree.class); + /** * A tree of all pending user writes (user-initiated set()'s, transaction()'s, update()'s, etc.). */ @@ -90,14 +94,13 @@ public class SyncTree { private final Set keepSyncedQueries; private final ListenProvider listenProvider; private final PersistenceManager persistenceManager; - private final LogWrapper logger; /** Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more views. */ private ImmutableTree syncPointTree; /** Static tracker for next query tag. */ private long nextQueryTag = 1L; public SyncTree( - Context context, PersistenceManager persistenceManager, ListenProvider listenProvider) { + PersistenceManager persistenceManager, ListenProvider listenProvider) { this.syncPointTree = ImmutableTree.emptyInstance(); this.pendingWriteTree = new WriteTree(); this.tagToQueryMap = new HashMap<>(); @@ -105,7 +108,6 @@ public SyncTree( this.keepSyncedQueries = new HashSet<>(); this.listenProvider = listenProvider; this.persistenceManager = persistenceManager; - this.logger = context.getLogger(SyncTree.class); } public boolean isEmpty() { @@ -968,7 +970,7 @@ public List onListenComplete(DatabaseError error) { return SyncTree.this.applyListenComplete(query.getPath()); } } else { - logger.warn("Listen at " + view.getQuery().getPath() + " failed: " + error.toString()); + logger.warn("Listen at {} failed: {}", view.getQuery().getPath(), error); // If a listen failed, kill all of the listeners here, not just the one that triggered the // error. Note that this may need to be scoped to just this listener if we change diff --git a/src/main/java/com/google/firebase/database/core/ThreadInitializer.java b/src/main/java/com/google/firebase/database/core/ThreadInitializer.java deleted file mode 100644 index 23c3cab88..000000000 --- a/src/main/java/com/google/firebase/database/core/ThreadInitializer.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.database.core; - -import java.lang.Thread.UncaughtExceptionHandler; - -public interface ThreadInitializer { - - ThreadInitializer defaultInstance = - new ThreadInitializer() { - @Override - public void setName(Thread t, String name) { - t.setName(name); - } - - @Override - public void setDaemon(Thread t, boolean isDaemon) { - t.setDaemon(isDaemon); - } - - @Override - public void setUncaughtExceptionHandler(Thread t, UncaughtExceptionHandler handler) { - t.setUncaughtExceptionHandler(handler); - } - }; - - void setName(Thread t, String name); - - void setDaemon(Thread t, boolean isDaemon); - - void setUncaughtExceptionHandler(Thread t, Thread.UncaughtExceptionHandler handler); -} diff --git a/src/main/java/com/google/firebase/database/core/ThreadPoolEventTarget.java b/src/main/java/com/google/firebase/database/core/ThreadPoolEventTarget.java index abe599fdc..34162fbe2 100644 --- a/src/main/java/com/google/firebase/database/core/ThreadPoolEventTarget.java +++ b/src/main/java/com/google/firebase/database/core/ThreadPoolEventTarget.java @@ -16,18 +16,15 @@ package com.google.firebase.database.core; -import static com.google.common.base.Preconditions.checkNotNull; - +import com.google.firebase.internal.FirebaseScheduledExecutor; import java.lang.Thread.UncaughtExceptionHandler; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** ThreadPoolEventTarget is an event target using a configurable threadpool. */ +/** ThreadPoolEventTarget is an event target using a configurable thread pool. */ class ThreadPoolEventTarget implements EventTarget, UncaughtExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(ThreadPoolEventTarget.class); @@ -35,26 +32,9 @@ class ThreadPoolEventTarget implements EventTarget, UncaughtExceptionHandler { private final ThreadPoolExecutor executor; private UncaughtExceptionHandler exceptionHandler; - public ThreadPoolEventTarget( - final ThreadFactory wrappedFactory, final ThreadInitializer threadInitializer) { - int poolSize = 1; - BlockingQueue queue = new LinkedBlockingQueue<>(); - - executor = new ThreadPoolExecutor(poolSize, poolSize, 3, TimeUnit.SECONDS, queue, - new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread thread = wrappedFactory.newThread(r); - threadInitializer.setName(thread, "FirebaseDatabaseEventTarget"); - threadInitializer.setDaemon(thread, true); - threadInitializer.setUncaughtExceptionHandler(thread, ThreadPoolEventTarget.this); - return thread; - } - }); - } - - public ThreadPoolEventTarget(final ThreadPoolExecutor executor) { - this.executor = checkNotNull(executor); + ThreadPoolEventTarget(ThreadFactory threadFactory) { + executor = new FirebaseScheduledExecutor(threadFactory, "firebase-database-event-target", this); + executor.setKeepAliveTime(3, TimeUnit.SECONDS); } @Override diff --git a/src/main/java/com/google/firebase/database/core/persistence/DefaultPersistenceManager.java b/src/main/java/com/google/firebase/database/core/persistence/DefaultPersistenceManager.java index 1bc546295..40e1b99dc 100644 --- a/src/main/java/com/google/firebase/database/core/persistence/DefaultPersistenceManager.java +++ b/src/main/java/com/google/firebase/database/core/persistence/DefaultPersistenceManager.java @@ -17,12 +17,10 @@ package com.google.firebase.database.core.persistence; import com.google.firebase.database.core.CompoundWrite; -import com.google.firebase.database.core.Context; import com.google.firebase.database.core.Path; import com.google.firebase.database.core.UserWriteRecord; import com.google.firebase.database.core.view.CacheNode; import com.google.firebase.database.core.view.QuerySpec; -import com.google.firebase.database.logging.LogWrapper; import com.google.firebase.database.snapshot.ChildKey; import com.google.firebase.database.snapshot.EmptyNode; import com.google.firebase.database.snapshot.IndexedNode; @@ -34,24 +32,26 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class DefaultPersistenceManager implements PersistenceManager { + private static final Logger logger = LoggerFactory.getLogger(PersistenceManager.class); + private final PersistenceStorageEngine storageLayer; private final TrackedQueryManager trackedQueryManager; - private final LogWrapper logger; private final CachePolicy cachePolicy; private long serverCacheUpdatesSinceLastPruneCheck = 0; public DefaultPersistenceManager( - Context ctx, PersistenceStorageEngine engine, CachePolicy cachePolicy) { - this(ctx, engine, cachePolicy, new DefaultClock()); + PersistenceStorageEngine engine, CachePolicy cachePolicy) { + this(engine, cachePolicy, new DefaultClock()); } public DefaultPersistenceManager( - Context ctx, PersistenceStorageEngine engine, CachePolicy cachePolicy, Clock clock) { + PersistenceStorageEngine engine, CachePolicy cachePolicy, Clock clock) { this.storageLayer = engine; - this.logger = ctx.getLogger(PersistenceManager.class); this.trackedQueryManager = new TrackedQueryManager(storageLayer, logger, clock); this.cachePolicy = cachePolicy; } @@ -253,15 +253,11 @@ public T runInTransaction(Callable callable) { private void doPruneCheckAfterServerUpdate() { serverCacheUpdatesSinceLastPruneCheck++; if (cachePolicy.shouldCheckCacheSize(serverCacheUpdatesSinceLastPruneCheck)) { - if (logger.logsDebug()) { - logger.debug("Reached prune check threshold."); - } + logger.debug("Reached prune check threshold."); serverCacheUpdatesSinceLastPruneCheck = 0; boolean canPrune = true; long cacheSize = storageLayer.serverCacheEstimatedSizeInBytes(); - if (logger.logsDebug()) { - logger.debug("Cache size: " + cacheSize); - } + logger.debug("Cache size: {}", cacheSize); while (canPrune && cachePolicy.shouldPrune(cacheSize, trackedQueryManager.countOfPrunableQueries())) { PruneForest pruneForest = this.trackedQueryManager.pruneOldQueries(cachePolicy); @@ -271,9 +267,7 @@ private void doPruneCheckAfterServerUpdate() { canPrune = false; } cacheSize = storageLayer.serverCacheEstimatedSizeInBytes(); - if (logger.logsDebug()) { - logger.debug("Cache size after prune: " + cacheSize); - } + logger.debug("Cache size after prune: {}", cacheSize); } } } diff --git a/src/main/java/com/google/firebase/database/core/persistence/TrackedQueryManager.java b/src/main/java/com/google/firebase/database/core/persistence/TrackedQueryManager.java index b4f6b545c..20278b947 100644 --- a/src/main/java/com/google/firebase/database/core/persistence/TrackedQueryManager.java +++ b/src/main/java/com/google/firebase/database/core/persistence/TrackedQueryManager.java @@ -23,7 +23,6 @@ import com.google.firebase.database.core.utilities.Predicate; import com.google.firebase.database.core.view.QueryParams; import com.google.firebase.database.core.view.QuerySpec; -import com.google.firebase.database.logging.LogWrapper; import com.google.firebase.database.snapshot.ChildKey; import com.google.firebase.database.utilities.Clock; import com.google.firebase.database.utilities.Utilities; @@ -36,6 +35,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.slf4j.Logger; public class TrackedQueryManager { @@ -74,7 +74,7 @@ public boolean evaluate(TrackedQuery query) { }; // DB, where we permanently store tracked queries. private final PersistenceStorageEngine storageLayer; - private final LogWrapper logger; + private final Logger logger; private final Clock clock; // In-memory cache of tracked queries. Should always be in-sync with the DB. private ImmutableTree> trackedQueryTree; @@ -82,7 +82,7 @@ public boolean evaluate(TrackedQuery query) { private long currentQueryId = 0; public TrackedQueryManager( - PersistenceStorageEngine storageLayer, LogWrapper logger, Clock clock) { + PersistenceStorageEngine storageLayer, Logger logger, Clock clock) { this.storageLayer = storageLayer; this.logger = logger; this.clock = clock; @@ -226,13 +226,8 @@ public PruneForest pruneOldQueries(CachePolicy cachePolicy) { long countToPrune = calculateCountToPrune(cachePolicy, prunable.size()); PruneForest forest = new PruneForest(); - if (logger.logsDebug()) { - logger.debug( - "Pruning old queries. Prunable: " - + prunable.size() - + " Count to prune: " - + countToPrune); - } + logger.debug( + "Pruning old queries. Prunable: {} Count to prune: {}", prunable.size(), countToPrune); Collections.sort( prunable, @@ -257,9 +252,7 @@ public int compare(TrackedQuery q1, TrackedQuery q2) { // Also keep the unprunable queries. List unprunable = getQueriesMatching(IS_QUERY_UNPRUNABLE_PREDICATE); - if (logger.logsDebug()) { - logger.debug("Unprunable queries: " + unprunable.size()); - } + logger.debug("Unprunable queries: {}", unprunable.size()); for (TrackedQuery toKeep : unprunable) { forest = forest.keep(toKeep.querySpec.getPath()); } diff --git a/src/main/java/com/google/firebase/database/core/view/EventRaiser.java b/src/main/java/com/google/firebase/database/core/view/EventRaiser.java index 47579abd8..d2bbacee6 100644 --- a/src/main/java/com/google/firebase/database/core/view/EventRaiser.java +++ b/src/main/java/com/google/firebase/database/core/view/EventRaiser.java @@ -18,10 +18,11 @@ import com.google.firebase.database.core.Context; import com.google.firebase.database.core.EventTarget; -import com.google.firebase.database.logging.LogWrapper; import java.util.ArrayList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Each view owns an instance of this class, and it is used to send events to the event target @@ -33,18 +34,16 @@ */ public class EventRaiser { + private static final Logger logger = LoggerFactory.getLogger(EventRaiser.class); + private final EventTarget eventTarget; - private final LogWrapper logger; public EventRaiser(Context ctx) { eventTarget = ctx.getEventTarget(); - logger = ctx.getLogger(EventRaiser.class); } public void raiseEvents(final List events) { - if (logger.logsDebug()) { - logger.debug("Raising " + events.size() + " event(s)"); - } + logger.debug("Raising {} event(s)", events.size()); // TODO: Use an immutable data structure for events so we don't have to clone to be safe. final ArrayList eventsClone = new ArrayList<>(events); eventTarget.postEvent( @@ -52,9 +51,7 @@ public void raiseEvents(final List events) { @Override public void run() { for (Event event : eventsClone) { - if (logger.logsDebug()) { - logger.debug("Raising " + event.toString()); - } + logger.debug("Raising {}", event); event.fire(); } } diff --git a/src/main/java/com/google/firebase/database/logging/DefaultLogger.java b/src/main/java/com/google/firebase/database/logging/DefaultLogger.java deleted file mode 100644 index d9ba110c7..000000000 --- a/src/main/java/com/google/firebase/database/logging/DefaultLogger.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.database.logging; - -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -public class DefaultLogger implements Logger { - - private final Set enabledComponents; - private final Level minLevel; - - public DefaultLogger(Level level, List enabledComponents) { - if (enabledComponents != null) { - this.enabledComponents = new HashSet<>(enabledComponents); - } else { - this.enabledComponents = null; - } - minLevel = level; - } - - @Override - public Level getLogLevel() { - return this.minLevel; - } - - @Override - public void onLogMessage(Level level, String tag, String message, long msTimestamp) { - if (shouldLog(level, tag)) { - String toLog = buildLogMessage(level, tag, message, msTimestamp); - switch (level) { - case ERROR: - error(tag, toLog); - break; - case WARN: - warn(tag, toLog); - break; - case INFO: - info(tag, toLog); - break; - case DEBUG: - debug(tag, toLog); - break; - default: - throw new RuntimeException("Should not reach here!"); - } - } - } - - protected String buildLogMessage(Level level, String tag, String message, long msTimestamp) { - Date now = new Date(msTimestamp); - return now.toString() + " " + "[" + level + "] " + tag + ": " + message; - } - - protected void error(String tag, String toLog) { - System.err.println(toLog); - } - - protected void warn(String tag, String toLog) { - System.out.println(toLog); - } - - protected void info(String tag, String toLog) { - System.out.println(toLog); - } - - protected void debug(String tag, String toLog) { - System.out.println(toLog); - } - - protected boolean shouldLog(Level level, String tag) { - return (level.ordinal() >= minLevel.ordinal() - && (enabledComponents == null - || level.ordinal() > Level.DEBUG.ordinal() - || enabledComponents.contains(tag))); - } -} diff --git a/src/main/java/com/google/firebase/database/logging/LogWrapper.java b/src/main/java/com/google/firebase/database/logging/LogWrapper.java deleted file mode 100644 index 21e28e526..000000000 --- a/src/main/java/com/google/firebase/database/logging/LogWrapper.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.database.logging; - -import java.io.PrintWriter; -import java.io.StringWriter; -import org.slf4j.LoggerFactory; - -/** - * Legacy logging interface for database implementation. This class attempts to reconcile SLF4J - * with the old logging implementation of the Admin SDK. When SLF4J is available, logs using that - * API. Otherwise falls back to the old logging implementation. This prevents individual log - * statements from being written to both log APIs. - * - * @deprecated This class will be removed in a future release, and SLF4J will be used universally - * throughout the codebase. - */ -public class LogWrapper { - - private final org.slf4j.Logger slf4jLogger; - private final Logger logger; - private final String component; - private final String prefix; - - public LogWrapper(Logger logger, Class component) { - this(logger, component, null); - } - - public LogWrapper(Logger logger, Class component, String prefix) { - this.slf4jLogger = LoggerFactory.getLogger(component); - this.logger = logger; - this.component = component.getName(); - this.prefix = prefix; - } - - public LogWrapper(Logger logger, String component, String prefix) { - this.slf4jLogger = LoggerFactory.getLogger(component); - this.logger = logger; - this.component = component; - this.prefix = prefix; - } - - private static String exceptionStacktrace(Throwable e) { - StringWriter writer = new StringWriter(); - PrintWriter printWriter = new PrintWriter(writer); - e.printStackTrace(printWriter); - return writer.toString(); - } - - public void error(String message, Throwable e) { - if (slf4jLogger.isErrorEnabled()) { - slf4jLogger.error(toLog(message), e); - } else { - String logMsg = toLog(message) + "\n" + exceptionStacktrace(e); - logger.onLogMessage(Logger.Level.ERROR, component, logMsg, now()); - } - } - - public void warn(String message) { - warn(message, null); - } - - public void warn(String message, Throwable e) { - if (slf4jLogger.isWarnEnabled()) { - slf4jLogger.warn(toLog(message), e); - } else { - String logMsg = toLog(message); - if (e != null) { - logMsg = logMsg + "\n" + exceptionStacktrace(e); - } - logger.onLogMessage(Logger.Level.WARN, component, logMsg, now()); - } - } - - public void info(String message) { - if (slf4jLogger.isInfoEnabled()) { - slf4jLogger.info(toLog(message)); - } else { - logger.onLogMessage(Logger.Level.INFO, component, toLog(message), now()); - } - } - - public void debug(String message, Object... args) { - this.debug(message, null, args); - } - - /** Log a non-fatal exception. Typically something like an IO error on a failed connection */ - public void debug(String message, Throwable e, Object... args) { - if (slf4jLogger.isDebugEnabled()) { - slf4jLogger.debug(toLog(message, args), e); - } else { - String logMsg = toLog(message, args); - if (e != null) { - logMsg = logMsg + "\n" + exceptionStacktrace(e); - } - logger.onLogMessage(Logger.Level.DEBUG, component, logMsg, now()); - } - } - - public boolean logsDebug() { - return this.logger.getLogLevel().ordinal() <= Logger.Level.DEBUG.ordinal() - || slf4jLogger.isDebugEnabled(); - } - - private long now() { - return System.currentTimeMillis(); - } - - private String toLog(String message, Object... args) { - String formatted = (args.length > 0) ? String.format(message, args) : message; - return prefix == null ? formatted : prefix + " - " + formatted; - } -} diff --git a/src/main/java/com/google/firebase/database/logging/Logger.java b/src/main/java/com/google/firebase/database/logging/Logger.java deleted file mode 100644 index 7fd2e6e0e..000000000 --- a/src/main/java/com/google/firebase/database/logging/Logger.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.database.logging; - -/** - * Private (internal) logging interface used by Firebase Database. See {@link - * com.google.firebase.database.core.DatabaseConfig DatabaseConfig} for more information. - * - * @deprecated Use SLF4J-based logging - */ -public interface Logger { - - /** - * This method will be triggered whenever the library has something to log - * - * @param level The level of the log message - * @param tag The component that this log message is coming from - * @param message The message to be logged - * @param msTimestamp The timestamp, in milliseconds, at which this message was generated - */ - void onLogMessage(Level level, String tag, String message, long msTimestamp); - - Level getLogLevel(); - - /** The log levels used by the Firebase Database library */ - enum Level { - DEBUG, - INFO, - WARN, - ERROR, - NONE - } -} diff --git a/src/main/java/com/google/firebase/database/utilities/DefaultRunLoop.java b/src/main/java/com/google/firebase/database/utilities/DefaultRunLoop.java index d43deb6b3..e84b7b561 100644 --- a/src/main/java/com/google/firebase/database/utilities/DefaultRunLoop.java +++ b/src/main/java/com/google/firebase/database/utilities/DefaultRunLoop.java @@ -18,13 +18,13 @@ import com.google.firebase.database.DatabaseException; import com.google.firebase.database.FirebaseDatabase; -import com.google.firebase.database.annotations.Nullable; -import com.google.firebase.database.core.Context; -import com.google.firebase.database.core.RepoManager; import com.google.firebase.database.core.RunLoop; -import com.google.firebase.internal.RevivingScheduledExecutor; +import com.google.firebase.internal.FirebaseScheduledExecutor; import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -33,43 +33,39 @@ public abstract class DefaultRunLoop implements RunLoop { - private ScheduledThreadPoolExecutor executor; + private final ScheduledThreadPoolExecutor executor; private UncaughtExceptionHandler exceptionHandler; - /** Creates a DefaultRunLoop that does not periodically restart its threads. */ - public DefaultRunLoop(ThreadFactory threadFactory) { - this(threadFactory, false, null); - } - /** * Creates a DefaultRunLoop that optionally restarts its threads periodically. If 'context' is * provided, these restarts will automatically interrupt and resume all Repo connections. */ - public DefaultRunLoop( - final ThreadFactory threadFactory, - final boolean periodicRestart, - @Nullable final Context context) { - executor = - new RevivingScheduledExecutor(threadFactory, "FirebaseDatabaseWorker", periodicRestart) { - @Override - protected void handleException(Throwable throwable) { - DefaultRunLoop.this.handleExceptionInternal(throwable); - } - - @Override - protected void beforeRestart() { - if (context != null) { - RepoManager.interrupt(context); + protected DefaultRunLoop(ThreadFactory threadFactory) { + executor = new FirebaseScheduledExecutor(threadFactory, "firebase-database-worker") { + @Override + protected void afterExecute(Runnable runnable, Throwable throwable) { + super.afterExecute(runnable, throwable); + if (throwable == null && runnable instanceof Future) { + Future future = (Future) runnable; + try { + // Not all Futures will be done, e.g. when used with scheduledAtFixedRate + if (future.isDone()) { + future.get(); } + } catch (CancellationException ce) { + // Cancellation exceptions are okay, we expect them to happen sometimes + } catch (ExecutionException ee) { + throwable = ee.getCause(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } + } - @Override - protected void afterRestart() { - if (context != null) { - RepoManager.resume(context); - } - } - }; + if (throwable != null) { + handleExceptionInternal(throwable); + } + } + }; // Core threads don't time out, this only takes effect when we drop the number of required // core threads diff --git a/src/main/java/com/google/firebase/database/utilities/Utilities.java b/src/main/java/com/google/firebase/database/utilities/Utilities.java index bd055d273..88c932e49 100644 --- a/src/main/java/com/google/firebase/database/utilities/Utilities.java +++ b/src/main/java/com/google/firebase/database/utilities/Utilities.java @@ -16,14 +16,14 @@ package com.google.firebase.database.utilities; +import com.google.api.core.ApiFuture; +import com.google.api.core.SettableApiFuture; import com.google.common.io.BaseEncoding; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseException; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.core.Path; import com.google.firebase.database.core.RepoInfo; -import com.google.firebase.tasks.Task; -import com.google.firebase.tasks.TaskCompletionSource; import java.io.UnsupportedEncodingException; import java.net.URI; @@ -237,22 +237,22 @@ public static void hardAssert(boolean condition, String message) { } } - public static Pair, DatabaseReference.CompletionListener> wrapOnComplete( + public static Pair, DatabaseReference.CompletionListener> wrapOnComplete( DatabaseReference.CompletionListener optListener) { if (optListener == null) { - final TaskCompletionSource source = new TaskCompletionSource<>(); + final SettableApiFuture future = SettableApiFuture.create(); DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { if (error != null) { - source.setException(error.toException()); + future.setException(error.toException()); } else { - source.setResult(null); + future.set(null); } } }; - return new Pair<>(source.getTask(), listener); + return new Pair, DatabaseReference.CompletionListener>(future, listener); } else { // If a listener is supplied we do not want to create a Task return new Pair<>(null, optListener); diff --git a/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java b/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java index 9057905ca..f3b6e5cb4 100644 --- a/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java +++ b/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java @@ -34,14 +34,13 @@ import com.google.common.io.ByteStreams; import com.google.firebase.FirebaseApp; import com.google.firebase.ImplFirebaseTrampolines; +import com.google.firebase.internal.CallableOperation; import com.google.firebase.internal.FirebaseRequestInitializer; import com.google.firebase.internal.FirebaseService; import com.google.firebase.internal.NonNull; -import com.google.firebase.internal.TaskToApiFuture; -import com.google.firebase.tasks.Task; +import java.io.IOException; import java.util.Map; -import java.util.concurrent.Callable; /** * This class is the entry point for all server-side Firebase Instance ID actions. @@ -118,37 +117,58 @@ void setInterceptor(HttpResponseInterceptor interceptor) { * pursuant to the General Data Protection Regulation (GDPR). * * @param instanceId A non-null, non-empty instance ID string. + * @throws IllegalArgumentException If the instance ID is null or empty. + * @throws FirebaseInstanceIdException If an error occurs while deleting the instance ID. + */ + public void deleteInstanceId(@NonNull String instanceId) throws FirebaseInstanceIdException { + deleteInstanceIdOp(instanceId).call(); + } + + /** + * Similar to {@link #deleteInstanceId(String)} but performs the operation asynchronously. + * + * @param instanceId A non-null, non-empty instance ID string. * @return An {@code ApiFuture} which will complete successfully when the instance ID is deleted, - * or unsuccessfully with the failure Exception.. + * or unsuccessfully with the failure Exception. + * @throws IllegalArgumentException If the instance ID is null or empty. */ public ApiFuture deleteInstanceIdAsync(@NonNull String instanceId) { - return new TaskToApiFuture<>(deleteInstanceId(instanceId)); + return deleteInstanceIdOp(instanceId).callAsync(app); } - private Task deleteInstanceId(final String instanceId) { + private CallableOperation deleteInstanceIdOp( + final String instanceId) { checkArgument(!Strings.isNullOrEmpty(instanceId), "instance ID must not be null or empty"); - return ImplFirebaseTrampolines.submitCallable(app, new Callable(){ + return new CallableOperation() { @Override - public Void call() throws Exception { + protected Void execute() throws FirebaseInstanceIdException { String url = String.format( "%s/project/%s/instanceId/%s", IID_SERVICE_URL, projectId, instanceId); - HttpRequest request = requestFactory.buildDeleteRequest(new GenericUrl(url)); - request.setParser(new JsonObjectParser(jsonFactory)); - request.setResponseInterceptor(interceptor); HttpResponse response = null; try { + HttpRequest request = requestFactory.buildDeleteRequest(new GenericUrl(url)); + request.setParser(new JsonObjectParser(jsonFactory)); + request.setResponseInterceptor(interceptor); response = request.execute(); ByteStreams.exhaust(response.getContent()); } catch (Exception e) { handleError(instanceId, e); } finally { - if (response != null) { - response.disconnect(); - } + disconnectQuietly(response); } return null; } - }); + }; + } + + private static void disconnectQuietly(HttpResponse response) { + if (response != null) { + try { + response.disconnect(); + } catch (IOException ignored) { + // ignored + } + } } private void handleError(String instanceId, Exception e) throws FirebaseInstanceIdException { diff --git a/src/main/java/com/google/firebase/internal/CallableOperation.java b/src/main/java/com/google/firebase/internal/CallableOperation.java new file mode 100644 index 000000000..ac28e8a7c --- /dev/null +++ b/src/main/java/com/google/firebase/internal/CallableOperation.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.internal; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.core.ApiFuture; +import com.google.firebase.FirebaseApp; +import com.google.firebase.ImplFirebaseTrampolines; +import java.util.concurrent.Callable; + +/** + * An operation that can be invoked synchronously or asynchronously. Subclasses can specify + * the return type and a specific exception type to be thrown. + */ +public abstract class CallableOperation implements Callable { + + protected abstract T execute() throws V; + + @Override + public final T call() throws V { + return execute(); + } + + /** + * Run this operation asynchronously on the main thread pool of the specified {@link FirebaseApp}. + * + * @param app A non-null {@link FirebaseApp}. + * @return An {@code ApiFuture}. + */ + public final ApiFuture callAsync(@NonNull FirebaseApp app) { + checkNotNull(app); + return ImplFirebaseTrampolines.submitCallable(app, this); + } +} diff --git a/src/main/java/com/google/firebase/internal/FirebaseScheduledExecutor.java b/src/main/java/com/google/firebase/internal/FirebaseScheduledExecutor.java new file mode 100644 index 000000000..0a472ba47 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/FirebaseScheduledExecutor.java @@ -0,0 +1,60 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.internal; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; + +/** + * A single-threaded scheduled executor implementation. Allows naming the threads, and spawns + * new threads as daemons. + */ +public class FirebaseScheduledExecutor extends ScheduledThreadPoolExecutor { + + public FirebaseScheduledExecutor(@NonNull ThreadFactory threadFactory, @NonNull String name) { + this(threadFactory, name, null); + } + + public FirebaseScheduledExecutor( + @NonNull ThreadFactory threadFactory, @NonNull String name, + @Nullable Thread.UncaughtExceptionHandler handler) { + super(1, decorateThreadFactory(threadFactory, name, handler)); + setRemoveOnCancelPolicy(true); + } + + static ThreadFactory getThreadFactoryWithName( + @NonNull ThreadFactory threadFactory, @NonNull String name) { + return decorateThreadFactory(threadFactory, name, null); + } + + private static ThreadFactory decorateThreadFactory( + ThreadFactory threadFactory, String name, Thread.UncaughtExceptionHandler handler) { + checkArgument(!Strings.isNullOrEmpty(name)); + ThreadFactoryBuilder builder = new ThreadFactoryBuilder() + .setThreadFactory(threadFactory) + .setNameFormat(name) + .setDaemon(true); + if (handler != null) { + builder.setUncaughtExceptionHandler(handler); + } + return builder.build(); + } +} diff --git a/src/main/java/com/google/firebase/internal/FirebaseThreadManagers.java b/src/main/java/com/google/firebase/internal/FirebaseThreadManagers.java index 0e503ba36..877450fcc 100644 --- a/src/main/java/com/google/firebase/internal/FirebaseThreadManagers.java +++ b/src/main/java/com/google/firebase/internal/FirebaseThreadManagers.java @@ -16,9 +16,6 @@ package com.google.firebase.internal; -import static com.google.common.base.Preconditions.checkState; - -import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.firebase.FirebaseApp; import com.google.firebase.ThreadManager; @@ -36,15 +33,7 @@ public class FirebaseThreadManagers { private static final Logger logger = LoggerFactory.getLogger(FirebaseThreadManagers.class); - public static final ThreadManager DEFAULT_THREAD_MANAGER; - - static { - if (GaeThreadFactory.isAvailable()) { - DEFAULT_THREAD_MANAGER = new GaeThreadManager(); - } else { - DEFAULT_THREAD_MANAGER = new DefaultThreadManager(); - } - } + public static final ThreadManager DEFAULT_THREAD_MANAGER = new DefaultThreadManager(); /** * An abstract ThreadManager implementation that uses the same executor service @@ -93,12 +82,8 @@ private static class DefaultThreadManager extends GlobalThreadManager { @Override protected ExecutorService doInit() { - // Create threads as daemons to ensure JVM exit when all foreground jobs are complete. - ThreadFactory threadFactory = new ThreadFactoryBuilder() - .setNameFormat("firebase-default-%d") - .setDaemon(true) - .setThreadFactory(getThreadFactory()) - .build(); + ThreadFactory threadFactory = FirebaseScheduledExecutor.getThreadFactoryWithName( + getThreadFactory(), "firebase-default-%d"); return Executors.newCachedThreadPool(threadFactory); } @@ -113,52 +98,4 @@ protected ThreadFactory getThreadFactory() { return Executors.defaultThreadFactory(); } } - - /** - * The ThreadManager implementation that will be used by default in the Google App Engine - * environment. - * - *

    Auto-scaling: Creates an ExecutorService backed by the request-scoped ThreadFactory. This - * can be used for any short-lived task, such as the ones submitted by components like - * FirebaseAuth. {@link #getThreadFactory()} throws an exception, since long-lived threads - * cannot be supported. Therefore task scheduling and RTDB will not work. - * - *

    Manual-scaling: Creates a single-threaded ExecutorService backed by the background - * ThreadFactory. Keeps the threads alive indefinitely by periodically restarting them (see - * {@link RevivingScheduledExecutor}). Threads will be terminated only when the method - * {@link #releaseExecutor(FirebaseApp, ExecutorService)} is invoked. The - * {@link #getThreadFactory()} also returns the background ThreadFactory enabling other - * components in the SDK to start long-lived threads when necessary. Therefore task scheduling - * and RTDB can be supported as if running on the regular JVM. - * - *

    Basic-scaling: Behavior is similar to manual-scaling. Since the threads are kept alive - * indefinitely, prevents the GAE idle instance shutdown. Developers are advised to use - * a custom ThreadManager implementation if idle instance shutdown should be supported. In - * general, a ThreadManager implementation that uses the request-scoped ThreadFactory, or the - * background ThreadFactory with specific keep-alive times can easily facilitate GAE idle - * instance shutdown. Note that this often comes at the cost of losing scheduled tasks and RTDB - * support. Therefore, for these features, manual-scaling is the recommended GAE deployment mode - * regardless of the ThreadManager implementation used. - */ - private static class GaeThreadManager extends GlobalThreadManager { - - @Override - protected ExecutorService doInit() { - return new GaeExecutorService("gae-firebase-default"); - } - - @Override - protected void doCleanup(ExecutorService executorService) { - executorService.shutdownNow(); - } - - @Override - protected ThreadFactory getThreadFactory() { - GaeThreadFactory threadFactory = GaeThreadFactory.getInstance(); - checkState(threadFactory.isUsingBackgroundThreads(), - "Failed to initialize a GAE background thread factory. Background thread support " - + "is required to create long-lived threads."); - return threadFactory; - } - } } diff --git a/src/main/java/com/google/firebase/internal/GaeExecutorService.java b/src/main/java/com/google/firebase/internal/GaeExecutorService.java deleted file mode 100644 index 6d1b323e8..000000000 --- a/src/main/java/com/google/firebase/internal/GaeExecutorService.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.internal; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; - -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; - -import java.util.Collection; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicReference; - -/** - * An ExecutorService instance that can operate in the Google App Engine environment. When - * available, uses background thread support to initialize an ExecutorService with long-lived - * threads. Otherwise, creates an ExecutorService that spawns short-lived threads as tasks - * are submitted. The actual ExecutorService implementation is lazy-loaded to prevent making - * unnecessary RPC calls to the GAE's native ThreadFactory mechanism. - */ -class GaeExecutorService implements ExecutorService { - - private final AtomicReference executor = new AtomicReference<>(); - private final String threadName; - private final ThreadFactory threadFactory; - private boolean shutdown; - - GaeExecutorService(String threadName) { - this(threadName, GaeThreadFactory.getInstance()); - } - - GaeExecutorService(String threadName, ThreadFactory threadFactory) { - checkArgument(!Strings.isNullOrEmpty(threadName)); - this.threadName = threadName; - this.threadFactory = threadFactory; - } - - private ExecutorService ensureExecutorService() { - ExecutorService executorService = executor.get(); - if (executorService == null) { - synchronized (executor) { - checkState(!shutdown); - executorService = executor.get(); - if (executorService == null) { - executorService = newExecutorService(threadFactory, threadName); - executor.compareAndSet(null, executorService); - } - } - } - return executorService; - } - - @Override - public Future submit(Callable task) { - return ensureExecutorService().submit(task); - } - - @Override - public Future submit(Runnable task, T result) { - return ensureExecutorService().submit(task, result); - } - - @Override - public Future submit(Runnable task) { - return ensureExecutorService().submit(task); - } - - @Override - public List> invokeAll(Collection> tasks) - throws InterruptedException { - return ensureExecutorService().invokeAll(tasks); - } - - @Override - public List> invokeAll( - Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException { - return ensureExecutorService().invokeAll(tasks, timeout, unit); - } - - @Override - public T invokeAny(Collection> tasks) - throws InterruptedException, ExecutionException { - return ensureExecutorService().invokeAny(tasks); - } - - @Override - public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return ensureExecutorService().invokeAny(tasks, timeout, unit); - } - - @Override - public void shutdown() { - synchronized (executor) { - ExecutorService executorService = executor.get(); - if (executorService != null && !shutdown) { - executorService.shutdown(); - } - shutdown = true; - } - } - - @Override - public List shutdownNow() { - synchronized (executor) { - ExecutorService executorService = executor.get(); - List result; - if (executorService != null && !shutdown) { - result = executorService.shutdownNow(); - } else { - result = ImmutableList.of(); - } - shutdown = true; - return result; - } - } - - @Override - public boolean isShutdown() { - synchronized (executor) { - return shutdown; - } - } - - @Override - public boolean isTerminated() { - synchronized (executor) { - if (!shutdown) { - return false; - } - ExecutorService executorService = executor.get(); - return executorService == null || executorService.isTerminated(); - } - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - ExecutorService executorService; - synchronized (executor) { - executorService = executor.get(); - } - // call await outside the lock - return executorService == null || executorService.awaitTermination(timeout, unit); - } - - @Override - public void execute(Runnable command) { - ensureExecutorService().execute(command); - } - - private static ExecutorService newExecutorService( - ThreadFactory threadFactory, String threadName) { - boolean background = threadFactory instanceof GaeThreadFactory - && ((GaeThreadFactory) threadFactory).isUsingBackgroundThreads(); - if (background) { - // Create a thread pool with long-lived threads if background thread support is available. - return new RevivingScheduledExecutor(threadFactory, threadName, true); - } else { - // Create an executor that creates a new thread for each submitted task, when background - // thread support is not available. - return new ThreadPoolExecutor( - 0, - Integer.MAX_VALUE, - 0L, - TimeUnit.SECONDS, - new SynchronousQueue(), - threadFactory); - } - } -} diff --git a/src/main/java/com/google/firebase/internal/GaeThreadFactory.java b/src/main/java/com/google/firebase/internal/GaeThreadFactory.java deleted file mode 100644 index 3791dad78..000000000 --- a/src/main/java/com/google/firebase/internal/GaeThreadFactory.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.internal; - -import static com.google.common.base.Preconditions.checkNotNull; - -import java.lang.reflect.InvocationTargetException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicReference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * GaeThreadFactory is a thread factory that works on App Engine. It uses background threads on - * manually-scaled GAE backends and request-scoped threads on automatically scaled instances. - * - *

    This class is thread-safe. - */ -public class GaeThreadFactory implements ThreadFactory { - - private static final Logger logger = LoggerFactory.getLogger(GaeThreadFactory.class); - - public static final ExecutorService DEFAULT_EXECUTOR = - new GaeExecutorService("LegacyFirebaseDefault"); - private static final String GAE_THREAD_MANAGER_CLASS = "com.google.appengine.api.ThreadManager"; - private static final GaeThreadFactory instance = new GaeThreadFactory(); - private final AtomicReference threadFactory = new AtomicReference<>(null); - - private GaeThreadFactory() {} - - public static GaeThreadFactory getInstance() { - return instance; - } - - /** Returns whether GaeThreadFactory can be used on this system (true for GAE). */ - public static boolean isAvailable() { - try { - Class.forName(GAE_THREAD_MANAGER_CLASS); - return System.getProperty("com.google.appengine.runtime.environment") != null; - } catch (ClassNotFoundException e) { - return false; - } - } - - private static ThreadFactory createBackgroundFactory() - throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, - IllegalAccessException { - Class gaeThreadManager = Class.forName(GAE_THREAD_MANAGER_CLASS); - return (ThreadFactory) gaeThreadManager.getMethod("backgroundThreadFactory").invoke(null); - } - - private static ThreadFactory createRequestScopedFactory() - throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, - IllegalAccessException { - Class gaeThreadManager = Class.forName(GAE_THREAD_MANAGER_CLASS); - return (ThreadFactory) gaeThreadManager.getMethod("currentRequestThreadFactory").invoke(null); - } - - @Override - public Thread newThread(Runnable r) { - ThreadFactoryWrapper wrapper = threadFactory.get(); - if (wrapper != null) { - return wrapper.getThreadFactory().newThread(r); - } - return initThreadFactory(r); - } - - /** - * Checks whether background thread support is available in the current environment. This method - * forces the ThreadFactory to get fully initialized (if not already initialized), by running a - * no-op thread. - * - * @return true if background thread support is available, and false otherwise. - */ - boolean isUsingBackgroundThreads() { - ThreadFactoryWrapper wrapper = threadFactory.get(); - if (wrapper != null) { - return wrapper.isUsingBackgroundThreads(); - } - - // Create a no-op thread to force initialize the ThreadFactory implementation. - // Start the resulting thread, since GAE code seems to expect that. - initThreadFactory(new Runnable() { - @Override - public void run() {} - }).start(); - return threadFactory.get().isUsingBackgroundThreads(); - } - - private Thread initThreadFactory(Runnable r) { - ThreadFactory threadFactory; - boolean usesBackgroundThreads = false; - Thread thread; - // Since we can't tell manually-scaled GAE instances apart until we spawn a thread (which - // sends an RPC and thus is done after class initialization), we initialize both of GAE's - // thread factories here and discard one once we detect that we are running in an - // automatically scaled instance. - // - // Note: It's fine if multiple threads access this block at the same time. - try { - try { - threadFactory = createBackgroundFactory(); - thread = threadFactory.newThread(r); - usesBackgroundThreads = true; - } catch (IllegalStateException e) { - logger.info("Falling back to GAE's request-scoped threads. Firebase requires " - + "manually-scaled instances for most operations."); - threadFactory = createRequestScopedFactory(); - thread = threadFactory.newThread(r); - } - } catch (ClassNotFoundException - | InvocationTargetException - | NoSuchMethodException - | IllegalAccessException e) { - threadFactory = - new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - logger.warn("Failed to initialize native GAE thread factory. " - + "GaeThreadFactory cannot be used in a non-GAE environment."); - return null; - } - }; - thread = null; - } - - ThreadFactoryWrapper wrapper = new ThreadFactoryWrapper(threadFactory, usesBackgroundThreads); - this.threadFactory.compareAndSet(null, wrapper); - return thread; - } - - private static class ThreadFactoryWrapper { - - private final ThreadFactory threadFactory; - private final boolean usingBackgroundThreads; - - private ThreadFactoryWrapper(ThreadFactory threadFactory, boolean usingBackgroundThreads) { - this.threadFactory = checkNotNull(threadFactory); - this.usingBackgroundThreads = usingBackgroundThreads; - } - - ThreadFactory getThreadFactory() { - return threadFactory; - } - - boolean isUsingBackgroundThreads() { - return usingBackgroundThreads; - } - } -} diff --git a/src/main/java/com/google/firebase/tasks/RuntimeExecutionException.java b/src/main/java/com/google/firebase/internal/ListenableFuture2ApiFuture.java similarity index 54% rename from src/main/java/com/google/firebase/tasks/RuntimeExecutionException.java rename to src/main/java/com/google/firebase/internal/ListenableFuture2ApiFuture.java index de460dd9b..18d98da7e 100644 --- a/src/main/java/com/google/firebase/tasks/RuntimeExecutionException.java +++ b/src/main/java/com/google/firebase/internal/ListenableFuture2ApiFuture.java @@ -14,16 +14,19 @@ * limitations under the License. */ -package com.google.firebase.tasks; +package com.google.firebase.internal; + +import com.google.api.core.ApiFuture; +import com.google.common.util.concurrent.ForwardingListenableFuture.SimpleForwardingListenableFuture; +import com.google.common.util.concurrent.ListenableFuture; /** - * Runtime version of {@link java.util.concurrent.ExecutionException}. - * - * @see Task#getResult(Class) + * Adapter from Guava ListenableFuture to GAX ApiFuture. */ -public class RuntimeExecutionException extends RuntimeException { +public class ListenableFuture2ApiFuture extends SimpleForwardingListenableFuture implements + ApiFuture { - public RuntimeExecutionException(Throwable cause) { - super(cause); + public ListenableFuture2ApiFuture(ListenableFuture delegate) { + super(delegate); } } diff --git a/src/main/java/com/google/firebase/internal/RevivingScheduledExecutor.java b/src/main/java/com/google/firebase/internal/RevivingScheduledExecutor.java deleted file mode 100644 index efa989943..000000000 --- a/src/main/java/com/google/firebase/internal/RevivingScheduledExecutor.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.internal; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.annotations.VisibleForTesting; - -import java.security.AccessControlException; -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.RunnableScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * RevivingScheduledExecutor is an implementation of ScheduledThreadPoolExecutor that uses one - * periodically restarting worker thread as its work queue. This allows customers of this class to - * use this executor on App Engine despite App Engine's thread-lifetime limitations. - */ -public class RevivingScheduledExecutor extends ScheduledThreadPoolExecutor { - - private static final Logger logger = LoggerFactory.getLogger(RevivingScheduledExecutor.class); - - /** Exception to throw to shut down the core threads. */ - private static final RuntimeException REVIVE_THREAD_EXCEPTION = new RuntimeException( - "Restarting Firebase Worker Thread. This exception is expected to occur periodically " - + "when deployed in the App Engine environment, and can be ignored."); - - /** The lifetime of a thread. Maximum lifetime of a thread on GAE is 24 hours. */ - private static final long PERIODIC_RESTART_INTERVAL_MS = TimeUnit.HOURS.toMillis(12); - - /** - * Time by which we offset restarts to ensure that not all threads die at the same time. This is - * meant to decrease cross-thread liveliness issues during restarts. - */ - private static final long PERIODIC_RESTART_OFFSET_MS = TimeUnit.MINUTES.toMillis(5); - - private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(0); - - private final long initialDelayMs; - private final long timeoutMs; - - // Flag set before throwing a REVIVE_THREAD_EXCEPTION and unset once a new thread has been - // created. Used to call afterRestart() appropriately. - private AtomicBoolean requestedRestart = new AtomicBoolean(); - - /** - * Creates a new RevivingScheduledExecutor that optionally restarts its worker thread every twelve - * hours. - * - * @param threadFactory Thread factory to use to restart threads. - * @param threadName Name of the threads in the pool. - * @param periodicRestart Periodically restart its worked threads. - */ - public RevivingScheduledExecutor( - final ThreadFactory threadFactory, final String threadName, final boolean periodicRestart) { - this( - threadFactory, - threadName, - periodicRestart ? PERIODIC_RESTART_OFFSET_MS * INSTANCE_COUNTER.get() : 0, - periodicRestart ? PERIODIC_RESTART_INTERVAL_MS : -1); - } - - @VisibleForTesting - RevivingScheduledExecutor( - final ThreadFactory threadFactory, - final String threadName, - final long initialDelayMs, - final long timeoutMs) { - super(0); - checkNotNull(threadFactory, "threadFactory must not be null"); - INSTANCE_COUNTER.incrementAndGet(); - this.initialDelayMs = initialDelayMs; - this.timeoutMs = timeoutMs; - setRemoveOnCancelPolicy(true); - setThreadFactory( - new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - logger.debug("Creating new thread for: {}", threadName); - Thread thread = threadFactory.newThread(r); - try { - thread.setName(threadName); - thread.setDaemon(true); - } catch (AccessControlException ignore) { - // Unsupported on App Engine. - } - if (requestedRestart.getAndSet(false)) { - afterRestart(); - } - return thread; - } - }); - } - - @Override - public void execute(Runnable runnable) { - // This gets called when the execute() method from Executor is directly invoked. - ensureRunning(); - super.execute(runnable); - } - - @Override - protected RunnableScheduledFuture decorateTask( - Runnable runnable, RunnableScheduledFuture task) { - // This gets called by ScheduledThreadPoolExecutor before scheduling a Runnable. - ensureRunning(); - return task; - } - - @Override - protected RunnableScheduledFuture decorateTask( - Callable callable, RunnableScheduledFuture task) { - // This gets called by ScheduledThreadPoolExecutor before scheduling a Callable. - ensureRunning(); - return task; - } - - @Override - protected void afterExecute(Runnable runnable, Throwable throwable) { - super.afterExecute(runnable, throwable); - if (throwable == null && runnable instanceof Future) { - Future future = (Future) runnable; - try { - // Not all Futures will be done, e.g. when used with scheduledAtFixedRate - if (future.isDone()) { - future.get(); - } - } catch (CancellationException ce) { - // Cancellation exceptions are okay, we expect them to happen sometimes - } catch (ExecutionException ee) { - throwable = ee.getCause(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - if (throwable == REVIVE_THREAD_EXCEPTION) { - // Re-throwing this exception will kill the thread and cause - // ScheduledThreadPoolExecutor to - // spawn a new thread. - throw (RuntimeException) throwable; - } else if (throwable != null) { - handleException(throwable); - } - } - - /** - * Called when an exception occurs during execution of a Runnable/Callable. The default - * implementation does nothing. - */ - protected void handleException(Throwable throwable) {} - - /** Called before the worker thread gets shutdown before a restart. */ - protected void beforeRestart() {} - - /** Called after the worker thread got recreated after a restart. */ - protected void afterRestart() {} - - private synchronized void ensureRunning() { - if (getCorePoolSize() == 0) { - setCorePoolSize(1); - schedulePeriodicShutdown(); - } - } - - private void schedulePeriodicShutdown() { - if (timeoutMs >= 0) { - @SuppressWarnings("unused") - Future possiblyIgnoredError = - schedule( - new Runnable() { - @Override - public void run() { - // We have to manually reschedule this task here as periodic tasks get - // cancelled after - // throwing exceptions. - @SuppressWarnings("unused") - Future possiblyIgnoredError1 = - RevivingScheduledExecutor.this.schedule( - this, timeoutMs, TimeUnit.MILLISECONDS); - requestedRestart.set(true); - beforeRestart(); - throw REVIVE_THREAD_EXCEPTION; - } - }, - initialDelayMs + timeoutMs, - TimeUnit.MILLISECONDS); - } - } -} diff --git a/src/main/java/com/google/firebase/internal/TaskToApiFuture.java b/src/main/java/com/google/firebase/internal/TaskToApiFuture.java deleted file mode 100644 index 238ba43a3..000000000 --- a/src/main/java/com/google/firebase/internal/TaskToApiFuture.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.internal; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.api.core.ApiFuture; -import com.google.firebase.tasks.OnCompleteListener; -import com.google.firebase.tasks.Task; -import com.google.firebase.tasks.Tasks; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * An ApiFuture implementation that wraps a {@link Task}. This is an interim solution that enables - * us to expose Tasks as ApiFutures, until we fully remove the Task API. - * - * @param Type of the result produced by this Future. - */ -public class TaskToApiFuture implements ApiFuture { - - private final Task task; - private boolean cancelled; - - public TaskToApiFuture(Task task) { - this.task = checkNotNull(task, "task must not be null"); - } - - @Override - public void addListener(final Runnable runnable, Executor executor) { - task.addOnCompleteListener(executor, new OnCompleteListener() { - @Override - public void onComplete(Task task) { - runnable.run(); - } - }); - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - // Cannot be supported with Tasks - cancelled = true; - return false; - } - - @Override - public boolean isCancelled() { - return false; - } - - @Override - public boolean isDone() { - return cancelled || task.isComplete(); - } - - @Override - public T get() throws InterruptedException, ExecutionException { - return Tasks.await(task); - } - - @Override - public T get(long timeout, @NonNull TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return Tasks.await(task, timeout, unit); - } -} diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java index e303ac863..83bed681c 100644 --- a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java +++ b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java @@ -38,15 +38,13 @@ import com.google.common.collect.ImmutableMap; import com.google.firebase.FirebaseApp; import com.google.firebase.ImplFirebaseTrampolines; +import com.google.firebase.internal.CallableOperation; import com.google.firebase.internal.FirebaseRequestInitializer; import com.google.firebase.internal.FirebaseService; import com.google.firebase.internal.NonNull; -import com.google.firebase.internal.TaskToApiFuture; -import com.google.firebase.tasks.Task; import java.io.IOException; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; /** * This class is the entry point for all server-side Firebase Cloud Messaging actions. @@ -139,11 +137,10 @@ public static synchronized FirebaseMessaging getInstance(FirebaseApp app) { * Sends the given {@link Message} via Firebase Cloud Messaging. * * @param message A non-null {@link Message} to be sent. - * @return An {@code ApiFuture} that will complete with a message ID string when the message - * has been sent. + * @return A message ID string. */ - public ApiFuture sendAsync(@NonNull Message message) { - return sendAsync(message, false); + public String send(@NonNull Message message) throws FirebaseMessagingException { + return send(message, false); } /** @@ -154,25 +151,50 @@ public ApiFuture sendAsync(@NonNull Message message) { * * @param message A non-null {@link Message} to be sent. * @param dryRun a boolean indicating whether to perform a dry run (validation only) of the send. + * @return A message ID string. + */ + public String send(@NonNull Message message, boolean dryRun) throws FirebaseMessagingException { + return sendOp(message, dryRun).call(); + } + + /** + * Similar to {@link #send(Message)} but performs the operation asynchronously. + * + * @param message A non-null {@link Message} to be sent. + * @return An {@code ApiFuture} that will complete with a message ID string when the message + * has been sent. + */ + public ApiFuture sendAsync(@NonNull Message message) { + return sendAsync(message, false); + } + + /** + * Similar to {@link #send(Message, boolean)} but performs the operation asynchronously. + * + * @param message A non-null {@link Message} to be sent. + * @param dryRun a boolean indicating whether to perform a dry run (validation only) of the send. * @return An {@code ApiFuture} that will complete with a message ID string when the message * has been sent, or when the emulation has finished. */ public ApiFuture sendAsync(@NonNull Message message, boolean dryRun) { - return new TaskToApiFuture<>(send(message, dryRun)); + return sendOp(message, dryRun).callAsync(app); } - private Task send(final Message message, final boolean dryRun) { - checkNotNull(message, "message must not be null"); - return ImplFirebaseTrampolines.submitCallable(app, new Callable() { - @Override - public String call() throws FirebaseMessagingException { - return makeSendRequest(message, dryRun); - } - }); + /** + * Subscribes a list of registration tokens to a topic. + * + * @param registrationTokens A non-null, non-empty list of device registration tokens, with at + * most 1000 entries. + * @param topic Name of the topic to subscribe to. May contain the {@code /topics/} prefix. + * @return A {@link TopicManagementResponse}. + */ + public TopicManagementResponse subscribeToTopic(@NonNull List registrationTokens, + @NonNull String topic) throws FirebaseMessagingException { + return manageTopicOp(registrationTokens, topic, IID_SUBSCRIBE_PATH).call(); } /** - * Subscribes a list of registration tokens to a topic. + * Similar to {@link #subscribeToTopic(List, String)} but performs the operation asynchronously. * * @param registrationTokens A non-null, non-empty list of device registration tokens, with at * most 1000 entries. @@ -181,24 +203,25 @@ public String call() throws FirebaseMessagingException { */ public ApiFuture subscribeToTopicAsync( @NonNull List registrationTokens, @NonNull String topic) { - return new TaskToApiFuture<>(subscribeToTopic(registrationTokens, topic)); + return manageTopicOp(registrationTokens, topic, IID_SUBSCRIBE_PATH).callAsync(app); } - private Task subscribeToTopic( - final List registrationTokens, final String topic) { - checkRegistrationTokens(registrationTokens); - checkTopic(topic); - - return ImplFirebaseTrampolines.submitCallable(app, new Callable() { - @Override - public TopicManagementResponse call() throws FirebaseMessagingException { - return makeTopicManagementRequest(registrationTokens, topic, IID_SUBSCRIBE_PATH); - } - }); + /** + * Unubscribes a list of registration tokens from a topic. + * + * @param registrationTokens A non-null, non-empty list of device registration tokens, with at + * most 1000 entries. + * @param topic Name of the topic to unsubscribe from. May contain the {@code /topics/} prefix. + * @return A {@link TopicManagementResponse}. + */ + public TopicManagementResponse unsubscribeFromTopic(@NonNull List registrationTokens, + @NonNull String topic) throws FirebaseMessagingException { + return manageTopicOp(registrationTokens, topic, IID_UNSUBSCRIBE_PATH).call(); } /** - * Unubscribes a list of registration tokens from a topic. + * Similar to {@link #unsubscribeFromTopic(List, String)} but performs the operation + * asynchronously. * * @param registrationTokens A non-null, non-empty list of device registration tokens, with at * most 1000 entries. @@ -207,48 +230,42 @@ public TopicManagementResponse call() throws FirebaseMessagingException { */ public ApiFuture unsubscribeFromTopicAsync( @NonNull List registrationTokens, @NonNull String topic) { - return new TaskToApiFuture<>(unsubscribeFromTopic(registrationTokens, topic)); + return manageTopicOp(registrationTokens, topic, IID_UNSUBSCRIBE_PATH) + .callAsync(app); } - private Task unsubscribeFromTopic( - final List registrationTokens, final String topic) { - checkRegistrationTokens(registrationTokens); - checkTopic(topic); - - return ImplFirebaseTrampolines.submitCallable(app, new Callable() { + private CallableOperation sendOp( + final Message message, final boolean dryRun) { + checkNotNull(message, "message must not be null"); + return new CallableOperation() { @Override - public TopicManagementResponse call() throws FirebaseMessagingException { - return makeTopicManagementRequest(registrationTokens, topic, IID_UNSUBSCRIBE_PATH); + protected String execute() throws FirebaseMessagingException { + ImmutableMap.Builder payload = ImmutableMap.builder() + .put("message", message); + if (dryRun) { + payload.put("validate_only", true); + } + HttpResponse response = null; + try { + HttpRequest request = requestFactory.buildPostRequest( + new GenericUrl(url), new JsonHttpContent(jsonFactory, payload.build())); + request.setParser(new JsonObjectParser(jsonFactory)); + request.setResponseInterceptor(interceptor); + response = request.execute(); + MessagingServiceResponse parsed = new MessagingServiceResponse(); + jsonFactory.createJsonParser(response.getContent()).parseAndClose(parsed); + return parsed.name; + } catch (HttpResponseException e) { + handleSendHttpError(e); + return null; + } catch (IOException e) { + throw new FirebaseMessagingException( + INTERNAL_ERROR, "Error while calling FCM backend service", e); + } finally { + disconnectQuietly(response); + } } - }); - } - - private String makeSendRequest(Message message, - boolean dryRun) throws FirebaseMessagingException { - ImmutableMap.Builder payload = ImmutableMap.builder() - .put("message", message); - if (dryRun) { - payload.put("validate_only", true); - } - HttpResponse response = null; - try { - HttpRequest request = requestFactory.buildPostRequest( - new GenericUrl(url), new JsonHttpContent(jsonFactory, payload.build())); - request.setParser(new JsonObjectParser(jsonFactory)); - request.setResponseInterceptor(interceptor); - response = request.execute(); - MessagingServiceResponse parsed = new MessagingServiceResponse(); - jsonFactory.createJsonParser(response.getContent()).parseAndClose(parsed); - return parsed.name; - } catch (HttpResponseException e) { - handleSendHttpError(e); - return null; - } catch (IOException e) { - throw new FirebaseMessagingException( - INTERNAL_ERROR, "Error while calling FCM backend service", e); - } finally { - disconnectQuietly(response); - } + }; } private void handleSendHttpError(HttpResponseException e) throws FirebaseMessagingException { @@ -273,39 +290,50 @@ private void handleSendHttpError(HttpResponseException e) throws FirebaseMessagi throw new FirebaseMessagingException(code, msg, e); } - private TopicManagementResponse makeTopicManagementRequest(List registrationTokens, - String topic, String path) throws FirebaseMessagingException { - if (!topic.startsWith("/topics/")) { - topic = "/topics/" + topic; - } - Map payload = ImmutableMap.of( - "to", topic, - "registration_tokens", registrationTokens - ); - - final String url = String.format("%s/%s", IID_HOST, path); - HttpResponse response = null; - try { - HttpRequest request = requestFactory.buildPostRequest( - new GenericUrl(url), new JsonHttpContent(jsonFactory, payload)); - request.getHeaders().set("access_token_auth", "true"); - request.setParser(new JsonObjectParser(jsonFactory)); - request.setResponseInterceptor(interceptor); - response = request.execute(); - InstanceIdServiceResponse parsed = new InstanceIdServiceResponse(); - jsonFactory.createJsonParser(response.getContent()).parseAndClose(parsed); - checkState(parsed.results != null && !parsed.results.isEmpty(), - "unexpected response from topic management service"); - return new TopicManagementResponse(parsed.results); - } catch (HttpResponseException e) { - handleTopicManagementHttpError(e); - return null; - } catch (IOException e) { - throw new FirebaseMessagingException( - INTERNAL_ERROR, "Error while calling IID backend service", e); - } finally { - disconnectQuietly(response); - } + private CallableOperation + manageTopicOp( + final List registrationTokens, final String topic, final String path) { + checkRegistrationTokens(registrationTokens); + checkTopic(topic); + return new CallableOperation() { + @Override + protected TopicManagementResponse execute() throws FirebaseMessagingException { + final String prefixedTopic; + if (topic.startsWith("/topics/")) { + prefixedTopic = topic; + } else { + prefixedTopic = "/topics/" + topic; + } + Map payload = ImmutableMap.of( + "to", prefixedTopic, + "registration_tokens", registrationTokens + ); + + final String url = String.format("%s/%s", IID_HOST, path); + HttpResponse response = null; + try { + HttpRequest request = requestFactory.buildPostRequest( + new GenericUrl(url), new JsonHttpContent(jsonFactory, payload)); + request.getHeaders().set("access_token_auth", "true"); + request.setParser(new JsonObjectParser(jsonFactory)); + request.setResponseInterceptor(interceptor); + response = request.execute(); + InstanceIdServiceResponse parsed = new InstanceIdServiceResponse(); + jsonFactory.createJsonParser(response.getContent()).parseAndClose(parsed); + checkState(parsed.results != null && !parsed.results.isEmpty(), + "unexpected response from topic management service"); + return new TopicManagementResponse(parsed.results); + } catch (HttpResponseException e) { + handleTopicManagementHttpError(e); + return null; + } catch (IOException e) { + throw new FirebaseMessagingException( + INTERNAL_ERROR, "Error while calling IID backend service", e); + } finally { + disconnectQuietly(response); + } + } + }; } private void handleTopicManagementHttpError( diff --git a/src/main/java/com/google/firebase/tasks/Continuation.java b/src/main/java/com/google/firebase/tasks/Continuation.java deleted file mode 100644 index ef76b54e3..000000000 --- a/src/main/java/com/google/firebase/tasks/Continuation.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.firebase.internal.NonNull; - -/** - * A function that is called to continue execution after completion of a {@link Task}. - * - * @param the Task's result type - * @param the Continuation's result type - * @see Task#continueWith(Continuation) - * @see Task#continueWithTask(Continuation) - */ -public interface Continuation { - - /** - * Returns the result of applying this Continuation to {@code task}. - * - *

    To propagate failure from the completed Task call {@link Task#getResult()} and allow the - * {@link RuntimeExecutionException} to propagate. The RuntimeExecutionException will be unwrapped - * such that the Task returned by {@link Task#continueWith(Continuation)} or {@link - * Task#continueWithTask(Continuation)} fails with the original exception. - * - *

    To suppress specific failures call {@link Task#getResult(Class)} and catch the exception - * types of interest: - * - *

    -   * task.continueWith(new Continuation<String, String>() {
    -   * {@literal @}Override
    -   * public String then(Task<String> task) {
    -   * try {
    -   * return task.getResult(IOException.class);
    -   * } catch (FileNotFoundException e) {
    -   * return "Not found";
    -   * } catch (IOException e) {
    -   * return "Read failed";
    -   * }
    -   * }
    -   * }
    -   * 
    - * - *

    To suppress all failures guard any calls to {@link Task#getResult()} with {@link - * Task#isSuccessful()}: - * - *

    -   * task.continueWith(new Continuation<String, String>() {
    -   * {@literal @}Override
    -   * public String then(Task<String> task) {
    -   * if (task.isSuccessful()) {
    -   * return task.getResult();
    -   * } else {
    -   * return DEFAULT_VALUE;
    -   * }
    -   * }
    -   * }
    -   * 
    - * - * @param task the completed Task. Never null - * @throws Exception if the result couldn't be produced - */ - R then(@NonNull Task task) throws Exception; -} diff --git a/src/main/java/com/google/firebase/tasks/ContinueWithCompletionListener.java b/src/main/java/com/google/firebase/tasks/ContinueWithCompletionListener.java deleted file mode 100644 index 19c621368..000000000 --- a/src/main/java/com/google/firebase/tasks/ContinueWithCompletionListener.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.firebase.internal.NonNull; - -import java.util.concurrent.Executor; - -/** - * A {@link TaskCompletionListener} that wraps a {@link Continuation}. - */ -class ContinueWithCompletionListener implements TaskCompletionListener { - - private final Executor executor; - private final Continuation continuation; - private final TaskImpl continuationTask; - - public ContinueWithCompletionListener( - @NonNull Executor executor, - @NonNull Continuation continuation, - @NonNull TaskImpl continuationTask) { - this.executor = executor; - this.continuation = continuation; - this.continuationTask = continuationTask; - } - - @Override - public void onComplete(@NonNull final Task task) { - executor.execute( - new Runnable() { - @Override - public void run() { - R result; - try { - result = continuation.then(task); - } catch (RuntimeExecutionException e) { - if (e.getCause() instanceof Exception) { - continuationTask.setException((Exception) e.getCause()); - } else { - continuationTask.setException(e); - } - return; - } catch (Exception e) { - continuationTask.setException(e); - return; - } - - continuationTask.setResult(result); - } - }); - } - - @Override - public void cancel() { - throw new UnsupportedOperationException(); - } -} diff --git a/src/main/java/com/google/firebase/tasks/ContinueWithTaskCompletionListener.java b/src/main/java/com/google/firebase/tasks/ContinueWithTaskCompletionListener.java deleted file mode 100644 index ffbf88f90..000000000 --- a/src/main/java/com/google/firebase/tasks/ContinueWithTaskCompletionListener.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.firebase.internal.NonNull; - -import java.util.concurrent.Executor; - -/** - * A {@link TaskCompletionListener} that wraps a {@link Continuation} that returns a {@link Task}. - */ -class ContinueWithTaskCompletionListener - implements TaskCompletionListener, OnSuccessListener, OnFailureListener { - - private final Executor executor; - private final Continuation> continuation; - private final TaskImpl continuationTask; - - public ContinueWithTaskCompletionListener( - @NonNull Executor executor, - @NonNull Continuation> continuation, - @NonNull TaskImpl continuationTask) { - this.executor = executor; - this.continuation = continuation; - this.continuationTask = continuationTask; - } - - @Override - public void onComplete(@NonNull final Task task) { - executor.execute( - new Runnable() { - @Override - public void run() { - Task resultTask; - try { - resultTask = continuation.then(task); - } catch (RuntimeExecutionException e) { - if (e.getCause() instanceof Exception) { - continuationTask.setException((Exception) e.getCause()); - } else { - continuationTask.setException(e); - } - return; - } catch (Exception e) { - continuationTask.setException(e); - return; - } - - if (resultTask == null) { - onFailure(new NullPointerException("Continuation returned null")); - return; - } - - resultTask.addOnSuccessListener( - TaskExecutors.DIRECT, ContinueWithTaskCompletionListener.this); - resultTask.addOnFailureListener( - TaskExecutors.DIRECT, ContinueWithTaskCompletionListener.this); - } - }); - } - - @Override - public void onSuccess(R result) { - continuationTask.setResult(result); - } - - @Override - public void onFailure(@NonNull Exception e) { - continuationTask.setException(e); - } - - @Override - public void cancel() { - throw new UnsupportedOperationException(); - } -} diff --git a/src/main/java/com/google/firebase/tasks/OnCompleteCompletionListener.java b/src/main/java/com/google/firebase/tasks/OnCompleteCompletionListener.java deleted file mode 100644 index e492fa596..000000000 --- a/src/main/java/com/google/firebase/tasks/OnCompleteCompletionListener.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.firebase.internal.GuardedBy; -import com.google.firebase.internal.NonNull; - -import java.util.concurrent.Executor; - -/** - * A {@link TaskCompletionListener} that wraps an {@link OnCompleteListener}. - */ -class OnCompleteCompletionListener implements TaskCompletionListener { - - private final Executor executor; - private final Object lock = new Object(); - - @GuardedBy("lock") - private OnCompleteListener onComplete; - - public OnCompleteCompletionListener( - @NonNull Executor executor, @NonNull OnCompleteListener onComplete) { - this.executor = executor; - this.onComplete = onComplete; - } - - @Override - public void onComplete(@NonNull final Task task) { - synchronized (lock) { - if (onComplete == null) { - return; - } - } - executor.execute( - new Runnable() { - @Override - public void run() { - synchronized (lock) { - if (onComplete != null) { - onComplete.onComplete(task); - } - } - } - }); - } - - @Override - public void cancel() { - synchronized (lock) { - onComplete = null; - } - } -} diff --git a/src/main/java/com/google/firebase/tasks/OnCompleteListener.java b/src/main/java/com/google/firebase/tasks/OnCompleteListener.java deleted file mode 100644 index b92fb8eb0..000000000 --- a/src/main/java/com/google/firebase/tasks/OnCompleteListener.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.firebase.internal.NonNull; - -/** - * Listener called when a {@link Task} completes. - * - * @param the Task's result type - * @see Task#addOnCompleteListener(OnCompleteListener) - */ -public interface OnCompleteListener { - - /** - * Called when the Task completes. - * - * @param task the completed Task. Never null - */ - void onComplete(@NonNull Task task); -} diff --git a/src/main/java/com/google/firebase/tasks/OnFailureCompletionListener.java b/src/main/java/com/google/firebase/tasks/OnFailureCompletionListener.java deleted file mode 100644 index 114d9eb62..000000000 --- a/src/main/java/com/google/firebase/tasks/OnFailureCompletionListener.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.firebase.internal.GuardedBy; -import com.google.firebase.internal.NonNull; - -import java.util.concurrent.Executor; - -/** - * A {@link TaskCompletionListener} that wraps an {@link OnFailureListener}. - */ -class OnFailureCompletionListener implements TaskCompletionListener { - - private final Executor executor; - private final Object lock = new Object(); - - @GuardedBy("lock") - private OnFailureListener onFailure; - - public OnFailureCompletionListener( - @NonNull Executor executor, @NonNull OnFailureListener onFailure) { - this.executor = executor; - this.onFailure = onFailure; - } - - @Override - public void onComplete(@NonNull final Task task) { - if (!task.isSuccessful()) { - synchronized (lock) { - if (onFailure == null) { - return; - } - } - executor.execute( - new Runnable() { - @Override - public void run() { - synchronized (lock) { - if (onFailure != null) { - onFailure.onFailure(task.getException()); - } - } - } - }); - } - } - - @Override - public void cancel() { - synchronized (lock) { - onFailure = null; - } - } -} diff --git a/src/main/java/com/google/firebase/tasks/OnFailureListener.java b/src/main/java/com/google/firebase/tasks/OnFailureListener.java deleted file mode 100644 index be44b9837..000000000 --- a/src/main/java/com/google/firebase/tasks/OnFailureListener.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.firebase.internal.NonNull; - -/** - * Listener called when a {@link Task} fails with an exception. - * - * @see Task#addOnFailureListener(OnFailureListener) - */ -public interface OnFailureListener { - - /** - * Called when the Task fails with an exception. - * - * @param e the exception that caused the Task to fail. Never null - */ - void onFailure(@NonNull Exception e); -} diff --git a/src/main/java/com/google/firebase/tasks/OnSuccessCompletionListener.java b/src/main/java/com/google/firebase/tasks/OnSuccessCompletionListener.java deleted file mode 100644 index 23a863c03..000000000 --- a/src/main/java/com/google/firebase/tasks/OnSuccessCompletionListener.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.firebase.internal.GuardedBy; -import com.google.firebase.internal.NonNull; - -import java.util.concurrent.Executor; - -/** - * A {@link TaskCompletionListener} that wraps an {@link OnSuccessListener}. - */ -class OnSuccessCompletionListener implements TaskCompletionListener { - - private final Executor executor; - private final Object lock = new Object(); - - @GuardedBy("lock") - private OnSuccessListener onSuccess; - - public OnSuccessCompletionListener( - @NonNull Executor executor, @NonNull OnSuccessListener onSuccess) { - this.executor = executor; - this.onSuccess = onSuccess; - } - - @Override - public void onComplete(@NonNull final Task task) { - if (task.isSuccessful()) { - synchronized (lock) { - if (onSuccess == null) { - return; - } - } - executor.execute( - new Runnable() { - @Override - public void run() { - synchronized (lock) { - if (onSuccess != null) { - onSuccess.onSuccess(task.getResult()); - } - } - } - }); - } - } - - @Override - public void cancel() { - synchronized (lock) { - onSuccess = null; - } - } -} diff --git a/src/main/java/com/google/firebase/tasks/OnSuccessListener.java b/src/main/java/com/google/firebase/tasks/OnSuccessListener.java deleted file mode 100644 index 3e84d5c10..000000000 --- a/src/main/java/com/google/firebase/tasks/OnSuccessListener.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -/** - * Listener called when a {@link Task} completes successfully. - * - * @param the Task's result type - * @see Task#addOnSuccessListener(OnSuccessListener) - */ -public interface OnSuccessListener { - - /** - * Called when the {@link Task} completes successfully. - * - * @param result the result of the Task - */ - void onSuccess(T result); -} diff --git a/src/main/java/com/google/firebase/tasks/Task.java b/src/main/java/com/google/firebase/tasks/Task.java deleted file mode 100644 index 13f3e85c0..000000000 --- a/src/main/java/com/google/firebase/tasks/Task.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.firebase.internal.NonNull; -import com.google.firebase.internal.Nullable; - -import java.util.concurrent.Executor; - -/** - * Represents an asynchronous operation. - * - * @param the type of the result of the operation - * @deprecated {@code Task} has been deprecated in favor of - *
    {@code ApiFuture}. - * For every method x() that returns a {@code Task}, you should be able to find a - * corresponding xAsync() method that returns an {@code ApiFuture}. - */ -public abstract class Task { - - /** - * Returns {@code true} if the Task is complete; {@code false} otherwise. - */ - public abstract boolean isComplete(); - - /** - * Returns {@code true} if the Task has completed successfully; {@code false} otherwise. - */ - public abstract boolean isSuccessful(); - - /** - * Gets the result of the Task, if it has already completed. - * - * @throws IllegalStateException if the Task is not yet complete - * @throws RuntimeExecutionException if the Task failed with an exception - */ - public abstract T getResult(); - - /** - * Gets the result of the Task, if it has already completed. - * - * @throws IllegalStateException if the Task is not yet complete - * @throws X if the Task failed with an exception of type X - * @throws RuntimeExecutionException if the Task failed with an exception that was not of type X - */ - public abstract T getResult(@NonNull Class exceptionType) throws X; - - /** - * Returns the exception that caused the Task to fail. Returns {@code null} if the Task is not yet - * complete, or completed successfully. - */ - @Nullable - public abstract Exception getException(); - - /** - * Adds a listener that is called if the Task completes successfully. - * - *

    The listener will be called on a shared thread pool. If the Task has already completed - * successfully, a call to the listener will be immediately scheduled. If multiple listeners are - * added, they will be called in the order in which they were added. - * - * @return this Task - * - * @deprecated Use {@link #addOnSuccessListener(Executor, OnSuccessListener)} - */ - @NonNull - public abstract Task addOnSuccessListener(@NonNull OnSuccessListener listener); - - /** - * Adds a listener that is called if the Task completes successfully. - * - *

    If multiple listeners are added, they will be called in the order in which they were added. - * If the Task has already completed successfully, a call to the listener will be immediately - * scheduled. - * - * @param executor the executor to use to call the listener - * @return this Task - */ - @NonNull - public abstract Task addOnSuccessListener( - @NonNull Executor executor, @NonNull OnSuccessListener listener); - - /** - * Adds a listener that is called if the Task fails. - * - *

    The listener will be called on a shared thread pool. If the Task has already failed, a call - * to the listener will be immediately scheduled. If multiple listeners are added, they will be - * called in the order in which they were added. - * - * @return this Task - * - * @deprecated Use {@link #addOnFailureListener(Executor, OnFailureListener)} - */ - @NonNull - public abstract Task addOnFailureListener(@NonNull OnFailureListener listener); - - /** - * Adds a listener that is called if the Task fails. - * - *

    If the Task has already failed, a call to the listener will be immediately scheduled. If - * multiple listeners are added, they will be called in the order in which they were added. - * - * @param executor the executor to use to call the listener - * @return this Task - */ - @NonNull - public abstract Task addOnFailureListener( - @NonNull Executor executor, @NonNull OnFailureListener listener); - - /** - * Adds a listener that is called when the Task completes. - * - *

    The listener will be called on a shared thread pool. If the Task is already complete, a call - * to the listener will be immediately scheduled. If multiple listeners are added, they will be - * called in the order in which they were added. - * - * @return this Task - * - * @deprecated Use {@link #addOnCompleteListener(Executor, OnCompleteListener)} - */ - @NonNull - public Task addOnCompleteListener(@NonNull OnCompleteListener listener) { - throw new UnsupportedOperationException("addOnCompleteListener is not implemented"); - } - - /** - * Adds a listener that is called when the Task completes. - * - *

    If the Task is already complete, a call to the listener will be immediately scheduled. If - * multiple listeners are added, they will be called in the order in which they were added. - * - * @param executor the executor to use to call the listener - * @return this Task - */ - @NonNull - public Task addOnCompleteListener( - @NonNull Executor executor, @NonNull OnCompleteListener listener) { - throw new UnsupportedOperationException("addOnCompleteListener is not implemented"); - } - - /** - * Returns a new Task that will be completed with the result of applying the specified - * Continuation to this Task. - * - *

    If the Continuation throws an exception, the returned Task will fail with that exception. - * - *

    The Continuation will be called on a shared thread pool. - * - * @deprecated Use {@link #continueWith(Executor, Continuation)}. - */ - @NonNull - public Task continueWith(@NonNull Continuation continuation) { - throw new UnsupportedOperationException("continueWith is not implemented"); - } - - /** - * Returns a new Task that will be completed with the result of applying the specified - * Continuation to this Task. - * - *

    If the Continuation throws an exception, the returned Task will fail with that exception. - * - * @param executor the executor to use to call the Continuation - * @see Continuation#then(Task) - */ - @NonNull - public Task continueWith( - @NonNull Executor executor, @NonNull Continuation continuation) { - throw new UnsupportedOperationException("continueWith is not implemented"); - } - - /** - * Returns a new Task that will be completed with the result of applying the specified - * Continuation to this Task. - * - *

    If the Continuation throws an exception, the returned Task will fail with that exception. - * - *

    The Continuation will be called on a shared thread pool. - * - * @deprecated Use {@link #continueWithTask(Executor, Continuation)} - */ - @NonNull - public Task continueWithTask(@NonNull Continuation> continuation) { - throw new UnsupportedOperationException("continueWithTask is not implemented"); - } - - /** - * Returns a new Task that will be completed with the result of applying the specified - * Continuation to this Task. - * - *

    If the Continuation throws an exception, the returned Task will fail with that exception. - * - * @param executor the executor to use to call the Continuation - * @see Continuation#then(Task) - */ - @NonNull - public Task continueWithTask( - @NonNull Executor executor, @NonNull Continuation> continuation) { - throw new UnsupportedOperationException("continueWithTask is not implemented"); - } -} diff --git a/src/main/java/com/google/firebase/tasks/TaskCompletionListener.java b/src/main/java/com/google/firebase/tasks/TaskCompletionListener.java deleted file mode 100644 index 2e261cab9..000000000 --- a/src/main/java/com/google/firebase/tasks/TaskCompletionListener.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.firebase.internal.NonNull; - -/** - * A listener that is called upon {@link Task} completion. - * - * @param Task result type. - */ -interface TaskCompletionListener { - - void onComplete(@NonNull Task task); - - void cancel(); -} diff --git a/src/main/java/com/google/firebase/tasks/TaskCompletionListenerQueue.java b/src/main/java/com/google/firebase/tasks/TaskCompletionListenerQueue.java deleted file mode 100644 index ef59e19fd..000000000 --- a/src/main/java/com/google/firebase/tasks/TaskCompletionListenerQueue.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.firebase.internal.GuardedBy; -import com.google.firebase.internal.NonNull; - -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.Queue; - -/** - * A queue of listeners to call upon {@link Task} completion. - * - * @param Task result type. - */ -class TaskCompletionListenerQueue { - - private final Object lock = new Object(); - - /** Lazily initialized, unbounded queue of listeners to call. */ - @GuardedBy("lock") - private Queue> queue; - - /** - * Indicates if a flush is already in progress. While this is true, further calls to flush() will - * do nothing. - */ - @GuardedBy("lock") - private boolean flushing; - - // TODO: Define behaviour for duplicate listeners. - public void add(@NonNull TaskCompletionListener listener) { - synchronized (lock) { - if (queue == null) { - queue = new ArrayDeque<>(); - } - queue.add(listener); - } - } - - public boolean removeAll(@NonNull Collection> listeners) { - synchronized (lock) { - return queue == null || queue.removeAll(listeners); - } - } - - public void flush(@NonNull Task task) { - synchronized (lock) { - if (queue == null || flushing) { - return; - } - flushing = true; - } - - while (true) { - TaskCompletionListener next; - synchronized (lock) { - next = queue.poll(); - if (next == null) { - flushing = false; - return; - } - } - - // Call outside the lock to avoid potential deadlocks with client code. - next.onComplete(task); - } - } -} diff --git a/src/main/java/com/google/firebase/tasks/TaskCompletionSource.java b/src/main/java/com/google/firebase/tasks/TaskCompletionSource.java deleted file mode 100644 index 12db74843..000000000 --- a/src/main/java/com/google/firebase/tasks/TaskCompletionSource.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.firebase.internal.NonNull; - -/** - * Provides the ability to create an incomplete {@link Task} and later complete it by either calling - * {@link #setResult} or {@link #setException}. - */ -public class TaskCompletionSource { - - private final TaskImpl task = new TaskImpl<>(); - - /** - * Completes the Task with the specified result. - * - * @throws IllegalStateException if the Task is already complete - */ - public void setResult(T result) { - task.setResult(result); - } - - /** - * Completes the Task with the specified result, unless the Task has already completed. If the - * Task has already completed, the call does nothing. - * - * @return {@code true} if the result was set successfully, {@code false} otherwise - */ - public boolean trySetResult(T result) { - return task.trySetResult(result); - } - - /** - * Completes the Task with the specified exception. - * - * @throws IllegalStateException if the Task is already complete - */ - public void setException(@NonNull Exception e) { - task.setException(e); - } - - /** - * Completes the Task with the specified exception, unless the Task has already completed. If the - * Task has already completed, the call does nothing. - * - * @return {@code true} if the exception was set successfully, {@code false} otherwise - */ - public boolean trySetException(@NonNull Exception e) { - return task.trySetException(e); - } - - /** Returns the Task. */ - @NonNull - public Task getTask() { - return task; - } -} diff --git a/src/main/java/com/google/firebase/tasks/TaskExecutors.java b/src/main/java/com/google/firebase/tasks/TaskExecutors.java deleted file mode 100644 index 19674d1d1..000000000 --- a/src/main/java/com/google/firebase/tasks/TaskExecutors.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.google.firebase.internal.GaeThreadFactory; -import com.google.firebase.internal.NonNull; - -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; - -/** - * Standard {@link Executor} instances for use with {@link Task}. - * - * @deprecated Use the ThreadManager interface to get required Executors. - */ -public class TaskExecutors { - - /** - * An Executor that uses a shared cached thread pool. - * - *

    This is no longer used in the SDK code. All the methods that submit to this thread pool - * have been deprecated, and their invocations have been routed elsewhere. This is left here - * for now for backward compatibility, since technically it is part of the public API. - */ - public static final Executor DEFAULT_THREAD_POOL; - /** An Executor that uses the calling thread. */ - static final Executor DIRECT = - new Executor() { - @Override - public void execute(@NonNull Runnable command) { - command.run(); - } - }; - - static { - if (GaeThreadFactory.isAvailable()) { - DEFAULT_THREAD_POOL = GaeThreadFactory.DEFAULT_EXECUTOR; - } else { - ThreadFactory threadFactory = new ThreadFactoryBuilder() - .setNameFormat("task-exec-%d") - .setDaemon(true) - .build(); - DEFAULT_THREAD_POOL = Executors.newCachedThreadPool(threadFactory); - } - } - - private TaskExecutors() {} -} diff --git a/src/main/java/com/google/firebase/tasks/TaskImpl.java b/src/main/java/com/google/firebase/tasks/TaskImpl.java deleted file mode 100644 index 095909e40..000000000 --- a/src/main/java/com/google/firebase/tasks/TaskImpl.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.base.Preconditions; -import com.google.firebase.internal.GuardedBy; -import com.google.firebase.internal.NonNull; -import com.google.firebase.internal.Nullable; - -import java.util.concurrent.Executor; - -/** - * Default implementation of {@link Task}. - */ -final class TaskImpl extends Task { - - private final Object lock = new Object(); - private final TaskCompletionListenerQueue listenerQueue = new TaskCompletionListenerQueue<>(); - - @GuardedBy("lock") - private boolean complete; - - @GuardedBy("lock") - private T result; - - @GuardedBy("lock") - private Exception exception; - - @Override - public boolean isComplete() { - synchronized (lock) { - return complete; - } - } - - @Override - public boolean isSuccessful() { - synchronized (lock) { - return complete && exception == null; - } - } - - @Override - public T getResult() { - synchronized (lock) { - checkCompleteLocked(); - - if (exception != null) { - throw new RuntimeExecutionException(exception); - } - - return result; - } - } - - @Override - public T getResult(@NonNull Class exceptionType) throws X { - synchronized (lock) { - checkCompleteLocked(); - - if (exceptionType.isInstance(exception)) { - throw exceptionType.cast(exception); - } - if (exception != null) { - throw new RuntimeExecutionException(exception); - } - - return result; - } - } - - public void setResult(T result) { - synchronized (lock) { - checkNotCompleteLocked(); - complete = true; - this.result = result; - } - // Intentionally outside the lock. - listenerQueue.flush(this); - } - - @Nullable - @Override - public Exception getException() { - synchronized (lock) { - return exception; - } - } - - @SuppressWarnings("ThrowableResultOfMethodCallIgnored") - public void setException(@NonNull Exception e) { - checkNotNull(e, "Exception must not be null"); - synchronized (lock) { - checkNotCompleteLocked(); - complete = true; - exception = e; - } - // Intentionally outside the lock. - listenerQueue.flush(this); - } - - @NonNull - @Override - public Task addOnSuccessListener(@NonNull OnSuccessListener listener) { - return addOnSuccessListener(TaskExecutors.DEFAULT_THREAD_POOL, listener); - } - - @NonNull - @Override - public Task addOnSuccessListener( - @NonNull Executor executor, @NonNull OnSuccessListener listener) { - listenerQueue.add(new OnSuccessCompletionListener<>(executor, listener)); - flushIfComplete(); - return this; - } - - @NonNull - @Override - public Task addOnFailureListener(@NonNull OnFailureListener listener) { - return addOnFailureListener(TaskExecutors.DEFAULT_THREAD_POOL, listener); - } - - @NonNull - @Override - public Task addOnFailureListener( - @NonNull Executor executor, @NonNull OnFailureListener listener) { - listenerQueue.add(new OnFailureCompletionListener(executor, listener)); - flushIfComplete(); - return this; - } - - @NonNull - @Override - public Task addOnCompleteListener(@NonNull OnCompleteListener listener) { - return addOnCompleteListener(TaskExecutors.DEFAULT_THREAD_POOL, listener); - } - - @NonNull - @Override - public Task addOnCompleteListener( - @NonNull Executor executor, @NonNull OnCompleteListener listener) { - listenerQueue.add(new OnCompleteCompletionListener<>(executor, listener)); - flushIfComplete(); - return this; - } - - @NonNull - @Override - public Task continueWith(@NonNull Continuation continuation) { - return continueWith(TaskExecutors.DEFAULT_THREAD_POOL, continuation); - } - - @NonNull - @Override - public Task continueWith( - @NonNull Executor executor, @NonNull Continuation continuation) { - TaskImpl continuationTask = new TaskImpl<>(); - listenerQueue.add( - new ContinueWithCompletionListener<>(executor, continuation, continuationTask)); - flushIfComplete(); - return continuationTask; - } - - @NonNull - @Override - public Task continueWithTask(@NonNull Continuation> continuation) { - return continueWithTask(TaskExecutors.DEFAULT_THREAD_POOL, continuation); - } - - @NonNull - @Override - public Task continueWithTask( - @NonNull Executor executor, @NonNull Continuation> continuation) { - TaskImpl continuationTask = new TaskImpl<>(); - listenerQueue.add( - new ContinueWithTaskCompletionListener<>(executor, continuation, continuationTask)); - flushIfComplete(); - return continuationTask; - } - - public boolean trySetResult(T result) { - synchronized (lock) { - if (complete) { - return false; - } - complete = true; - this.result = result; - } - // Intentionally outside the lock. - listenerQueue.flush(this); - return true; - } - - @SuppressWarnings("ThrowableResultOfMethodCallIgnored") - public boolean trySetException(@NonNull Exception e) { - checkNotNull(e, "Exception must not be null"); - synchronized (lock) { - if (complete) { - return false; - } - complete = true; - exception = e; - } - // Intentionally outside the lock. - listenerQueue.flush(this); - return true; - } - - @GuardedBy("lock") - private void checkCompleteLocked() { - Preconditions.checkState(complete, "Task is not yet complete"); - } - - @GuardedBy("lock") - private void checkNotCompleteLocked() { - Preconditions.checkState(!complete, "Task is already complete"); - } - - private void flushIfComplete() { - synchronized (lock) { - if (!complete) { - return; - } - } - // Intentionally outside the lock. - listenerQueue.flush(this); - } -} diff --git a/src/main/java/com/google/firebase/tasks/Tasks.java b/src/main/java/com/google/firebase/tasks/Tasks.java deleted file mode 100644 index 3671ac14f..000000000 --- a/src/main/java/com/google/firebase/tasks/Tasks.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.firebase.internal.GuardedBy; -import com.google.firebase.internal.NonNull; - -import java.util.Arrays; -import java.util.Collection; -import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * {@link Task} utility methods. - * - * @deprecated Use ApiFutures and ThreadManager interface instead. - */ -public final class Tasks { - - private Tasks() {} - - /** Returns a completed Task with the specified result. */ - public static Task forResult(T result) { - TaskImpl task = new TaskImpl<>(); - task.setResult(result); - return task; - } - - /** Returns a completed Task with the specified exception. */ - public static Task forException(@NonNull Exception exception) { - TaskImpl task = new TaskImpl<>(); - task.setException(exception); - return task; - } - - /** - * Returns a Task that will be completed with the result of the specified Callable. - * - *

    The Callable will be called on a shared thread pool. - * - * @deprecated Use {@link #call(Executor, Callable)} - */ - public static Task call(@NonNull Callable callable) { - return call(TaskExecutors.DEFAULT_THREAD_POOL, callable); - } - - /** - * Returns a Task that will be completed with the result of the specified Callable. - * - * @param executor the Executor to use to call the Callable - */ - public static Task call(@NonNull Executor executor, @NonNull final Callable callable) { - checkNotNull(executor, "Executor must not be null"); - checkNotNull(callable, "Callback must not be null"); - - final TaskImpl task = new TaskImpl<>(); - executor.execute( - new Runnable() { - @Override - public void run() { - try { - task.setResult(callable.call()); - } catch (Exception e) { - task.setException(e); - } - } - }); - return task; - } - - /** - * Blocks until the specified Task is complete. - * - * @return the Task's result - * @throws ExecutionException if the Task fails - * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete - */ - public static T await(@NonNull Task task) throws ExecutionException, InterruptedException { - checkNotNull(task, "Task must not be null"); - - if (task.isComplete()) { - return getResultOrThrowExecutionException(task); - } - - AwaitListener listener = new AwaitListener(); - addListener(task, listener); - listener.await(); - - return getResultOrThrowExecutionException(task); - } - - /** - * Blocks until the specified Task is complete. - * - * @return the Task's result - * @throws ExecutionException if the Task fails - * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete - * @throws TimeoutException if the specified timeout is reached before the Task completes - */ - public static T await(@NonNull Task task, long timeout, @NonNull TimeUnit unit) - throws ExecutionException, InterruptedException, TimeoutException { - checkNotNull(task, "Task must not be null"); - checkNotNull(unit, "TimeUnit must not be null"); - - if (task.isComplete()) { - return getResultOrThrowExecutionException(task); - } - - AwaitListener listener = new AwaitListener(); - addListener(task, listener); - - if (!listener.await(timeout, unit)) { - throw new TimeoutException("Timed out waiting for Task"); - } - - return getResultOrThrowExecutionException(task); - } - - /** - * Returns a Task that completes successfully when all of the specified Tasks complete - * successfully. Does not accept nulls. - * - * @throws NullPointerException if any of the provided Tasks are null - */ - public static Task whenAll(final Collection> tasks) { - if (tasks.isEmpty()) { - return Tasks.forResult(null); - } - for (Task task : tasks) { - if (task == null) { - throw new NullPointerException("null tasks are not accepted"); - } - } - TaskImpl whenAllTask = new TaskImpl<>(); - WhenAllListener listener = new WhenAllListener(tasks.size(), whenAllTask); - for (Task task : tasks) { - addListener(task, listener); - } - return whenAllTask; - } - - /** - * Returns a Task that completes successfully when all of the specified Tasks complete - * successfully. Does not accept nulls. - * - * @throws NullPointerException if any of the provided Tasks are null - */ - public static Task whenAll(Task... tasks) { - if (tasks.length == 0) { - return Tasks.forResult(null); - } - return whenAll(Arrays.asList(tasks)); - } - - private static T getResultOrThrowExecutionException(Task task) throws ExecutionException { - if (task.isSuccessful()) { - return task.getResult(); - } else { - throw new ExecutionException(task.getException()); - } - } - - private static void addListener(Task task, CombinedListener listener) { - // Use a direct executor to avoid an additional thread-hop. - task.addOnSuccessListener(TaskExecutors.DIRECT, listener); - task.addOnFailureListener(TaskExecutors.DIRECT, listener); - } - - interface CombinedListener extends OnSuccessListener, OnFailureListener {} - - private static final class AwaitListener implements CombinedListener { - - private final CountDownLatch latch = new CountDownLatch(1); - - @Override - public void onSuccess(Object obj) { - latch.countDown(); - } - - @Override - public void onFailure(@NonNull Exception exception) { - latch.countDown(); - } - - public void await() throws InterruptedException { - latch.await(); - } - - public boolean await(long timeout, TimeUnit unit) throws InterruptedException { - return latch.await(timeout, unit); - } - } - - private static final class WhenAllListener implements CombinedListener { - - private final Object lock = new Object(); - private final int numTasks; - private final TaskImpl task; - - @GuardedBy("lock") - private int successCounter; - - @GuardedBy("lock") - private int failuresCounter; - - @GuardedBy("lock") - private Exception exception; - - public WhenAllListener(int taskCount, TaskImpl task) { - numTasks = taskCount; - this.task = task; - } - - @Override - public void onFailure(@NonNull Exception exception) { - synchronized (lock) { - failuresCounter++; - this.exception = exception; - checkForCompletionLocked(); - } - } - - @Override - public void onSuccess(Object obj) { - synchronized (lock) { - successCounter++; - checkForCompletionLocked(); - } - } - - @GuardedBy("lock") - private void checkForCompletionLocked() { - if (successCounter + failuresCounter == numTasks) { - if (exception == null) { - task.setResult(null); - } else { - task.setException( - new ExecutionException( - failuresCounter + " out of " + numTasks + " underlying tasks failed", exception)); - } - } - } - } -} diff --git a/src/test/java/com/google/firebase/FirebaseAppTest.java b/src/test/java/com/google/firebase/FirebaseAppTest.java index d90679d05..e549c0955 100644 --- a/src/test/java/com/google/firebase/FirebaseAppTest.java +++ b/src/test/java/com/google/firebase/FirebaseAppTest.java @@ -16,7 +16,6 @@ package com.google.firebase; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; @@ -35,7 +34,6 @@ import com.google.auth.oauth2.OAuth2Credentials.CredentialsChangedListener; import com.google.common.base.Defaults; import com.google.common.collect.ImmutableMap; -import com.google.common.io.BaseEncoding; import com.google.firebase.FirebaseApp.TokenRefresher; import com.google.firebase.FirebaseOptions.Builder; import com.google.firebase.database.FirebaseDatabase; @@ -233,15 +231,6 @@ public void testInvokeAfterDeleteThrows() throws Exception { } } - @Test - public void testPersistenceKey() { - String name = "myApp"; - FirebaseApp firebaseApp = FirebaseApp.initializeApp(OPTIONS, name); - String persistenceKey = firebaseApp.getPersistenceKey(); - assertEquals(name, new String(BaseEncoding.base64Url().omitPadding().decode(persistenceKey), - UTF_8)); - } - // Order of test cases matters. @Test(expected = IllegalStateException.class) public void testMissingInit() { @@ -441,19 +430,19 @@ public void testEmptyFirebaseConfigJSONObject() { assertTrue(firebaseApp.getOptions().getDatabaseAuthVariableOverride().isEmpty()); } - @Test(expected = IllegalStateException.class) + @Test(expected = IllegalArgumentException.class) public void testInvalidFirebaseConfigFile() { setFirebaseConfigEnvironmentVariable("firebase_config_invalid.json"); FirebaseApp.initializeApp(); } - @Test(expected = IllegalStateException.class) + @Test(expected = IllegalArgumentException.class) public void testInvalidFirebaseConfigString() { setFirebaseConfigEnvironmentVariable("{,,"); FirebaseApp.initializeApp(); } - @Test(expected = IllegalStateException.class) + @Test(expected = IllegalArgumentException.class) public void testFirebaseConfigMissingFile() { setFirebaseConfigEnvironmentVariable("no_such.json"); FirebaseApp.initializeApp(); diff --git a/src/test/java/com/google/firebase/FirebaseOptionsTest.java b/src/test/java/com/google/firebase/FirebaseOptionsTest.java index 33e07aeb5..72a9c4784 100644 --- a/src/test/java/com/google/firebase/FirebaseOptionsTest.java +++ b/src/test/java/com/google/firebase/FirebaseOptionsTest.java @@ -27,12 +27,9 @@ import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.gson.GsonFactory; +import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; -import com.google.firebase.auth.FirebaseCredential; -import com.google.firebase.auth.FirebaseCredentials; -import com.google.firebase.auth.GoogleOAuthAccessToken; -import com.google.firebase.tasks.Task; import com.google.firebase.testing.ServiceAccount; import com.google.firebase.testing.TestUtils; import java.io.IOException; @@ -75,7 +72,7 @@ protected ThreadFactory getThreadFactory() { }; @Test - public void createOptionsWithAllValuesSet() throws IOException, InterruptedException { + public void createOptionsWithAllValuesSet() throws IOException { GsonFactory jsonFactory = new GsonFactory(); NetHttpTransport httpTransport = new NetHttpTransport(); FirebaseOptions firebaseOptions = @@ -108,7 +105,7 @@ public void createOptionsWithAllValuesSet() throws IOException, InterruptedExcep } @Test - public void createOptionsWithOnlyMandatoryValuesSet() throws IOException, InterruptedException { + public void createOptionsWithOnlyMandatoryValuesSet() throws IOException { FirebaseOptions firebaseOptions = new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) @@ -130,32 +127,12 @@ public void createOptionsWithOnlyMandatoryValuesSet() throws IOException, Interr } @Test - public void createOptionsWithFirebaseCredential() throws IOException { + public void createOptionsWithCustomFirebaseCredential() { FirebaseOptions firebaseOptions = new FirebaseOptions.Builder() - .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream())) - .build(); - - assertNotNull(firebaseOptions.getJsonFactory()); - assertNotNull(firebaseOptions.getHttpTransport()); - assertNull(firebaseOptions.getDatabaseUrl()); - assertNull(firebaseOptions.getStorageBucket()); - - GoogleCredentials credentials = firebaseOptions.getCredentials(); - assertNotNull(credentials); - assertTrue(credentials instanceof ServiceAccountCredentials); - assertEquals( - GoogleCredential.fromStream(ServiceAccount.EDITOR.asStream()).getServiceAccountId(), - ((ServiceAccountCredentials) credentials).getClientEmail()); - } - - @Test - public void createOptionsWithCustomFirebaseCredential() throws IOException { - FirebaseOptions firebaseOptions = - new FirebaseOptions.Builder() - .setCredential(new FirebaseCredential() { + .setCredentials(new GoogleCredentials() { @Override - public Task getAccessToken() { + public AccessToken refreshAccessToken() { return null; } }) diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java index e2c709ccd..bb430d285 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java @@ -106,16 +106,6 @@ public static Collection data() throws Exception { .build(), /* isCertCredential */ false }, - { - new FirebaseOptions.Builder().setCredential( - createFirebaseCertificateCredential()).build(), - /* isCertCredential */ true - }, - { - new FirebaseOptions.Builder().setCredential( - createFirebaseRefreshTokenCredential()).build(), - /* isCertCredential */ false - }, }); } @@ -141,23 +131,6 @@ public HttpTransport create() { }); } - private static FirebaseCredential createFirebaseRefreshTokenCredential() - throws IOException { - - final MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); - - Map secretJson = new HashMap<>(); - secretJson.put("client_id", CLIENT_ID); - secretJson.put("client_secret", CLIENT_SECRET); - secretJson.put("refresh_token", REFRESH_TOKEN); - secretJson.put("type", "authorized_user"); - InputStream refreshTokenStream = - new ByteArrayInputStream(JSON_FACTORY.toByteArray(secretJson)); - return FirebaseCredentials.fromRefreshToken(refreshTokenStream, transport, JSON_FACTORY); - } - private static GoogleCredentials createCertificateCredential() throws IOException { final MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); @@ -170,13 +143,6 @@ public HttpTransport create() { }); } - private static FirebaseCredential createFirebaseCertificateCredential() throws IOException { - final MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); - return FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream(), - transport, JSON_FACTORY); - } - @Before public void setup() { TestOnlyImplFirebaseTrampolines.clearInstancesForTest(); @@ -189,7 +155,7 @@ public void cleanup() { } @Test - public void testGetInstance() throws ExecutionException, InterruptedException { + public void testGetInstance() { FirebaseAuth defaultAuth = FirebaseAuth.getInstance(); assertNotNull(defaultAuth); assertSame(defaultAuth, FirebaseAuth.getInstance()); @@ -198,7 +164,7 @@ public void testGetInstance() throws ExecutionException, InterruptedException { } @Test - public void testGetInstanceForApp() throws ExecutionException, InterruptedException { + public void testGetInstanceForApp() { FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testGetInstanceForApp"); FirebaseAuth auth = FirebaseAuth.getInstance(app); assertNotNull(auth); @@ -208,7 +174,7 @@ public void testGetInstanceForApp() throws ExecutionException, InterruptedExcept } @Test - public void testAppDelete() throws ExecutionException, InterruptedException { + public void testAppDelete() { FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testAppDelete"); FirebaseAuth auth = FirebaseAuth.getInstance(app); assertNotNull(auth); @@ -272,7 +238,7 @@ public void testInitAfterAppDelete() throws ExecutionException, InterruptedExcep } @Test - public void testAppWithAuthVariableOverrides() throws ExecutionException, InterruptedException { + public void testAppWithAuthVariableOverrides() { Map authVariableOverrides = Collections.singletonMap("uid", (Object) "uid1"); FirebaseOptions options = new FirebaseOptions.Builder(firebaseOptions) @@ -335,7 +301,7 @@ public void testServiceAccountRequired() throws Exception { try { FirebaseAuth.getInstance(app).createCustomTokenAsync("foo").get(); fail("Expected exception."); - } catch (IllegalStateException expected) { + } catch (IllegalArgumentException expected) { Assert.assertEquals( "Must initialize FirebaseApp with a service account credential to call " + "createCustomToken()", @@ -353,7 +319,7 @@ public void testProjectIdRequired() throws Exception { try { FirebaseAuth.getInstance(app).verifyIdTokenAsync("foo").get(); fail("Expected exception."); - } catch (IllegalStateException expected) { + } catch (IllegalArgumentException expected) { Assert.assertEquals( "Must initialize FirebaseApp with a project ID to call verifyIdToken()", expected.getMessage()); diff --git a/src/test/java/com/google/firebase/auth/FirebaseCredentialsTest.java b/src/test/java/com/google/firebase/auth/FirebaseCredentialsTest.java deleted file mode 100644 index dd52e941a..000000000 --- a/src/test/java/com/google/firebase/auth/FirebaseCredentialsTest.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.auth; - -import com.google.api.client.googleapis.testing.auth.oauth2.MockTokenServerTransport; -import com.google.api.client.googleapis.util.Utils; -import com.google.api.client.json.JsonFactory; -import com.google.auth.oauth2.AccessToken; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.firebase.auth.internal.BaseCredential; -import com.google.firebase.auth.internal.FirebaseCredentialsAdapter; -import com.google.firebase.tasks.Task; -import com.google.firebase.tasks.Tasks; -import com.google.firebase.testing.ServiceAccount; -import com.google.firebase.testing.TestUtils; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -/** - * Tests for {@link FirebaseCredentials}. - */ -public class FirebaseCredentialsTest { - - private static final String ACCESS_TOKEN = "mockaccesstoken"; - private static final String CLIENT_SECRET = "mockclientsecret"; - private static final String CLIENT_ID = "mockclientid"; - private static final String REFRESH_TOKEN = "mockrefreshtoken"; - private static final JsonFactory JSON_FACTORY = Utils.getDefaultJsonFactory(); - - @Rule public final ExpectedException thrown = ExpectedException.none(); - - @BeforeClass - public static void setupClass() throws IOException { - TestUtils.getApplicationDefaultCredentials(); - } - - @Test(expected = NullPointerException.class) - public void testNullCertificate() throws IOException { - FirebaseCredentials.fromCertificate(null); - } - - @Test(expected = NullPointerException.class) - public void testNullRefreshToken() throws IOException { - FirebaseCredentials.fromRefreshToken(null); - } - - @Test - public void defaultCredentialDoesntRefetch() throws Exception { - FirebaseCredential credential = FirebaseCredentials.applicationDefault( - Utils.getDefaultTransport(), Utils.getDefaultJsonFactory()); - GoogleOAuthAccessToken token = Tasks.await(credential.getAccessToken()); - Assert.assertEquals(TestUtils.TEST_ADC_ACCESS_TOKEN, token.getAccessToken()); - Assert.assertNotNull(((BaseCredential) credential).getGoogleCredentials()); - - // We should still be able to fetch the token since the certificate is cached - credential = FirebaseCredentials.applicationDefault(); - token = Tasks.await(credential.getAccessToken()); - Assert.assertNotNull(token); - Assert.assertEquals(TestUtils.TEST_ADC_ACCESS_TOKEN, token.getAccessToken()); - Assert.assertNotNull(((BaseCredential) credential).getGoogleCredentials()); - } - - @Test - public void canResolveTokenMoreThanOnce() - throws ExecutionException, InterruptedException, IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); - - FirebaseCredential credential = - FirebaseCredentials.fromCertificate( - ServiceAccount.EDITOR.asStream(), transport, Utils.getDefaultJsonFactory()); - - Tasks.await(credential.getAccessToken()); - Tasks.await(credential.getAccessToken()); - } - - @Test - public void certificateReadIsDoneSynchronously() - throws ExecutionException, InterruptedException, IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); - - ByteArrayInputStream inputStream = - new ByteArrayInputStream( - ServiceAccount.EDITOR.asString().getBytes(Charset.defaultCharset())); - - FirebaseCredential credential = - FirebaseCredentials.fromCertificate(inputStream, transport, Utils.getDefaultJsonFactory()); - - Assert.assertEquals(0, inputStream.available()); - inputStream.close(); - - Assert.assertNotNull(((BaseCredential) credential).getGoogleCredentials()); - Assert.assertEquals(ACCESS_TOKEN, Tasks.await(credential.getAccessToken()).getAccessToken()); - } - - @Test - public void certificateReadChecksForProjectId() - throws ExecutionException, InterruptedException, IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); - - String accountWithoutProjectId = - ServiceAccount.EDITOR.asString().replace("project_id", "missing"); - ByteArrayInputStream inputStream = - new ByteArrayInputStream(accountWithoutProjectId.getBytes(Charset.defaultCharset())); - - try { - FirebaseCredentials.fromCertificate(inputStream, transport, Utils.getDefaultJsonFactory()); - Assert.fail(); - } catch (IllegalArgumentException e) { - Assert.assertEquals( - "Failed to parse service account: 'project_id' must be set", e.getMessage()); - } - } - - @Test - public void certificateReadThrowsIOException() - throws ExecutionException, InterruptedException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); - - InputStream inputStream = - new InputStream() { - @Override - public int read() throws IOException { - throw new IOException("Expected"); - } - }; - - - try { - FirebaseCredentials.fromCertificate(inputStream, transport, Utils.getDefaultJsonFactory()); - Assert.fail(); - } catch (IOException e) { - Assert.assertEquals("Expected", e.getMessage()); - } - } - - @Test - public void nullThrowsRuntimeExceptionFromCertificate() - throws ExecutionException, InterruptedException, IOException { - final MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); - thrown.expect(NullPointerException.class); - FirebaseCredentials.fromCertificate(null, transport, Utils.getDefaultJsonFactory()); - } - - @Test - public void nullThrowsRuntimeExceptionFromRefreshToken() - throws ExecutionException, InterruptedException, IOException { - final MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); - thrown.expect(NullPointerException.class); - FirebaseCredentials.fromRefreshToken(null, transport, Utils.getDefaultJsonFactory()); - } - - @Test - public void refreshTokenReadIsDoneSynchronously() - throws ExecutionException, InterruptedException, IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); - - Map secretJson = new HashMap<>(); - secretJson.put("client_id", CLIENT_ID); - secretJson.put("client_secret", CLIENT_SECRET); - secretJson.put("refresh_token", REFRESH_TOKEN); - secretJson.put("type", "authorized_user"); - InputStream inputStream = new ByteArrayInputStream(JSON_FACTORY.toByteArray(secretJson)); - - FirebaseCredential credential = - FirebaseCredentials.fromRefreshToken(inputStream, transport, Utils.getDefaultJsonFactory()); - - Assert.assertEquals(0, inputStream.available()); - inputStream.close(); - - Assert.assertNotNull(((BaseCredential) credential).getGoogleCredentials()); - Assert.assertEquals(ACCESS_TOKEN, Tasks.await(credential.getAccessToken()).getAccessToken()); - } - - @Test - public void refreshTokenReadThrowsIOException() - throws ExecutionException, InterruptedException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); - - InputStream inputStream = - new InputStream() { - @Override - public int read() throws IOException { - throw new IOException("Expected"); - } - }; - - try { - FirebaseCredentials.fromRefreshToken(inputStream, transport, Utils.getDefaultJsonFactory()); - Assert.fail(); - } catch (IOException e) { - Assert.assertEquals("Expected", e.getMessage()); - } - } - - @Test(expected = Exception.class) - public void serviceAccountUsedAsRefreshToken() throws Exception { - FirebaseCredentials.fromRefreshToken(ServiceAccount.EDITOR.asStream()); - } - - @Test - public void tokenNotCached() throws Exception { - TestCredential credential = new TestCredential(new MockGoogleCredentials(ACCESS_TOKEN, 10L)); - - for (long i = 0; i < 10; i++) { - Assert.assertEquals(ACCESS_TOKEN, Tasks.await(credential.getAccessToken()).getAccessToken()); - Assert.assertEquals(i + 1, credential.getFetchCount()); - } - } - - @Test - public void testTokenExpiration() throws Exception { - final MockGoogleCredentials googleCredentials = new MockGoogleCredentials(ACCESS_TOKEN); - TestCredential credential = new TestCredential(googleCredentials); - - for (long i = 0; i < 10; i++) { - long expiryTime = (i + 1) * 10L; - googleCredentials.setExpiryTime(expiryTime); - GoogleOAuthAccessToken googleToken = Tasks.await(credential.getAccessToken()); - Assert.assertEquals(ACCESS_TOKEN, googleToken.getAccessToken()); - Assert.assertEquals(expiryTime, googleToken.getExpiryTime()); - } - } - - @Test - public void testCustomFirebaseCredential() throws IOException { - final Date date = new Date(); - FirebaseCredential credential = new FirebaseCredential() { - @Override - public Task getAccessToken() { - return Tasks.forResult(new GoogleOAuthAccessToken("token", date.getTime())); - } - }; - GoogleCredentials googleCredentials = new FirebaseCredentialsAdapter(credential); - AccessToken accessToken = googleCredentials.refreshAccessToken(); - Assert.assertEquals("token", accessToken.getTokenValue()); - Assert.assertEquals(date, accessToken.getExpirationTime()); - } - - private static class TestCredential extends BaseCredential { - - private final AtomicInteger fetchCount = new AtomicInteger(0); - - TestCredential(GoogleCredentials googleCredentials) { - super(googleCredentials); - } - - @Override - public Task getAccessToken() { - fetchCount.incrementAndGet(); - return super.getAccessToken(); - } - - int getFetchCount() { - return fetchCount.get(); - } - } -} diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java index 25f5cde6c..b10058420 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java @@ -391,7 +391,7 @@ public void call(FirebaseAuth auth) throws Exception { operation.call(FirebaseAuth.getInstance()); fail("No error thrown for HTTP error: " + code); } catch (ExecutionException e) { - assertTrue(e.getCause().toString(), e.getCause() instanceof FirebaseAuthException); + assertTrue(e.getCause() instanceof FirebaseAuthException); FirebaseAuthException authException = (FirebaseAuthException) e.getCause(); String msg = String.format("Unexpected HTTP response with status: %d; body: {}", code); assertEquals(msg, authException.getMessage()); diff --git a/src/test/java/com/google/firebase/database/TestHelpers.java b/src/test/java/com/google/firebase/database/TestHelpers.java index da05f8adf..cfd15034d 100644 --- a/src/test/java/com/google/firebase/database/TestHelpers.java +++ b/src/test/java/com/google/firebase/database/TestHelpers.java @@ -20,7 +20,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import com.google.common.collect.ImmutableList; import com.google.firebase.FirebaseApp; import com.google.firebase.database.connection.ConnectionAuthTokenProvider; import com.google.firebase.database.connection.ConnectionContext; @@ -31,8 +30,6 @@ import com.google.firebase.database.core.RepoManager; import com.google.firebase.database.core.view.QuerySpec; import com.google.firebase.database.future.WriteFuture; -import com.google.firebase.database.logging.DefaultLogger; -import com.google.firebase.database.logging.Logger.Level; import com.google.firebase.database.snapshot.ChildKey; import com.google.firebase.database.util.JsonMapper; import com.google.firebase.database.utilities.DefaultRunLoop; @@ -67,7 +64,6 @@ public static DatabaseConfig newFrozenTestConfig(FirebaseApp app) { public static DatabaseConfig newTestConfig(FirebaseApp app) { DatabaseConfig config = new DatabaseConfig(); - config.setLogLevel(Logger.Level.WARN); config.setFirebaseApp(app); return config; } @@ -94,11 +90,6 @@ public static ScheduledExecutorService getExecutorService(DatabaseConfig config) return runLoop.getExecutorService(); } - public static void setLogger( - DatabaseConfig ctx, com.google.firebase.database.logging.Logger logger) { - ctx.setLogger(logger); - } - public static void waitFor(Semaphore semaphore) throws InterruptedException { waitFor(semaphore, 1); } @@ -323,15 +314,13 @@ public Object answer(InvocationOnMock invocation) throws Throwable { } public static ConnectionContext newConnectionContext(ScheduledExecutorService executor) { - com.google.firebase.database.logging.Logger logger = new DefaultLogger( - Level.NONE, ImmutableList.of()); ConnectionAuthTokenProvider tokenProvider = new ConnectionAuthTokenProvider() { @Override public void getToken(boolean forceRefresh, GetTokenCallback callback) { callback.onSuccess("gauth|{\"token\":\"test-token\"}"); } }; - return new ConnectionContext(logger, tokenProvider, executor, false, "testVersion", + return new ConnectionContext(tokenProvider, executor, false, "testVersion", "testUserAgent", Executors.defaultThreadFactory()); } diff --git a/src/test/java/com/google/firebase/database/connection/ConnectionTest.java b/src/test/java/com/google/firebase/database/connection/ConnectionTest.java index 56ff9a95a..4e413fb48 100644 --- a/src/test/java/com/google/firebase/database/connection/ConnectionTest.java +++ b/src/test/java/com/google/firebase/database/connection/ConnectionTest.java @@ -17,7 +17,6 @@ package com.google.firebase.database.connection; import com.google.common.collect.ImmutableMap; -import com.google.firebase.database.TestHelpers; import com.google.firebase.database.connection.Connection.Delegate; import com.google.firebase.database.connection.Connection.DisconnectReason; import com.google.firebase.database.connection.Connection.WebsocketConnectionFactory; @@ -187,7 +186,6 @@ private static class MockConnectionFactory implements WebsocketConnectionFactory MockConnectionFactory() { this.connection = new Connection( - TestHelpers.newConnectionContext(executor), Mockito.mock(HostInfo.class), delegate, this); diff --git a/src/test/java/com/google/firebase/database/core/GaePlatformTest.java b/src/test/java/com/google/firebase/database/core/GaePlatformTest.java deleted file mode 100644 index 032de1b4a..000000000 --- a/src/test/java/com/google/firebase/database/core/GaePlatformTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.google.firebase.database.core; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import com.google.common.collect.ImmutableList; -import com.google.firebase.FirebaseApp; -import com.google.firebase.FirebaseOptions; -import com.google.firebase.TestOnlyImplFirebaseTrampolines; -import com.google.firebase.ThreadManager; -import com.google.firebase.database.FirebaseDatabase; -import com.google.firebase.database.logging.Logger; -import com.google.firebase.internal.GaeThreadFactory; -import com.google.firebase.internal.NonNull; -import com.google.firebase.testing.ServiceAccount; -import com.google.firebase.testing.TestUtils; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; -import org.mockito.Mockito; - -public class GaePlatformTest { - - @Test - public void testIsActive() { - assertEquals(GaeThreadFactory.isAvailable(), GaePlatform.isActive()); - } - - @Test - public void testGaePlatform() { - final AtomicInteger count = new AtomicInteger(0); - final ThreadManager threadManager = new ThreadManager() { - @Override - protected ExecutorService getExecutor(@NonNull FirebaseApp app) { - return Executors.newSingleThreadExecutor(); - } - - @Override - protected void releaseExecutor(@NonNull FirebaseApp app, - @NonNull ExecutorService executor) { - } - - @Override - protected ThreadFactory getThreadFactory() { - count.incrementAndGet(); - return Executors.defaultThreadFactory(); - } - }; - - FirebaseOptions options = new FirebaseOptions.Builder() - .setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream())) - .setThreadManager(threadManager) - .build(); - FirebaseApp app = FirebaseApp.initializeApp(options, "gaeApp"); - try { - GaePlatform platform = new GaePlatform(app); - Context ctx = Mockito.mock(Context.class); - assertNotNull(platform.newLogger(ctx, Logger.Level.DEBUG, ImmutableList.of())); - - assertNotNull(platform.newEventTarget(ctx)); - assertEquals(1, count.get()); - - assertNotNull(platform.newRunLoop(ctx)); - assertEquals(2, count.get()); - - AuthTokenProvider authTokenProvider = platform.newAuthTokenProvider( - Mockito.mock(ScheduledExecutorService.class)); - assertTrue(authTokenProvider instanceof JvmAuthTokenProvider); - - assertEquals("AppEngine/AdminJava", platform.getUserAgent(ctx)); - assertEquals("gae-" + FirebaseDatabase.getSdkVersion(), platform.getPlatformVersion()); - assertNull(platform.createPersistenceManager(ctx, "test")); - - ThreadInitializer threadInitializer = platform.getThreadInitializer(); - Thread t = new Thread(); - threadInitializer.setName(t, "test-name"); - threadInitializer.setDaemon(t, true); - threadInitializer.setUncaughtExceptionHandler(t, Mockito.mock( - Thread.UncaughtExceptionHandler.class)); - assertNotEquals("test-name", t.getName()); - assertFalse(t.isDaemon()); - assertNotNull(t.getUncaughtExceptionHandler()); - - } finally { - TestOnlyImplFirebaseTrampolines.clearInstancesForTest(); - } - } -} diff --git a/src/test/java/com/google/firebase/database/core/SyncPointTest.java b/src/test/java/com/google/firebase/database/core/SyncPointTest.java index 4d70fd06b..91be0cce7 100644 --- a/src/test/java/com/google/firebase/database/core/SyncPointTest.java +++ b/src/test/java/com/google/firebase/database/core/SyncPointTest.java @@ -28,7 +28,6 @@ import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.InternalHelpers; import com.google.firebase.database.Query; -import com.google.firebase.database.TestHelpers; import com.google.firebase.database.annotations.NotNull; import com.google.firebase.database.connection.ListenHashProvider; import com.google.firebase.database.core.persistence.NoopPersistenceManager; @@ -37,9 +36,6 @@ import com.google.firebase.database.core.view.DataEvent; import com.google.firebase.database.core.view.Event; import com.google.firebase.database.core.view.QuerySpec; -import com.google.firebase.database.logging.DefaultLogger; -import com.google.firebase.database.logging.LogWrapper; -import com.google.firebase.database.logging.Logger; import com.google.firebase.database.snapshot.IndexedNode; import com.google.firebase.database.snapshot.Node; import com.google.firebase.database.snapshot.NodeUtilities; @@ -60,9 +56,13 @@ import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SyncPointTest { + private static final Logger logger = LoggerFactory.getLogger(SyncPointTest.class); + private static FirebaseApp testApp; @BeforeClass @@ -79,7 +79,7 @@ public static void tearDownClass() { TestOnlyImplFirebaseTrampolines.clearInstancesForTest(); } - private static SyncTree.ListenProvider getNewListenProvider(final LogWrapper logger) { + private static SyncTree.ListenProvider getNewListenProvider() { return new SyncTree.ListenProvider() { private final HashSet listens = new HashSet<>(); @@ -96,7 +96,7 @@ public void startListening( @Override public void stopListening(QuerySpec query, Tag tag) { Path path = query.getPath(); - logger.debug("Listening at " + path + " for Tag " + tag); + logger.debug("Listening at {} for Tag {}", path, tag); checkState(this.listens.contains(query), "Stopped listening for query already"); this.listens.remove(query); @@ -314,12 +314,9 @@ private static Map parseMergePaths(Map merges) { @SuppressWarnings("unchecked") private static void runTest(Map testSpec, String basePath) { - DatabaseConfig config = TestHelpers.newTestConfig(testApp); - TestHelpers.setLogger(config, new DefaultLogger(Logger.Level.WARN, null)); - LogWrapper logger = config.getLogger("SyncPointTest"); - logger.info("Running \"" + testSpec.get("name") + '"'); - SyncTree.ListenProvider listenProvider = getNewListenProvider(logger); - SyncTree syncTree = new SyncTree(config, new NoopPersistenceManager(), listenProvider); + logger.debug("Running \"{}\"", testSpec.get("name")); + SyncTree.ListenProvider listenProvider = getNewListenProvider(); + SyncTree syncTree = new SyncTree(new NoopPersistenceManager(), listenProvider); int currentWriteId = 0; diff --git a/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java b/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java index b06cfb841..99450944c 100644 --- a/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java +++ b/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java @@ -70,7 +70,7 @@ public static void tearDownClass() { private PersistenceManager newTestPersistenceManager() { MockPersistenceStorageEngine engine = new MockPersistenceStorageEngine(); engine.disableTransactionCheck = true; - return new DefaultPersistenceManager(newFrozenTestConfig(testApp), engine, CachePolicy.NONE); + return new DefaultPersistenceManager(engine, CachePolicy.NONE); } @Test @@ -131,7 +131,7 @@ public void applyUserMergeUsesRelativePath() { engine.overwriteServerCache(path(""), initialData); DefaultPersistenceManager manager = - new DefaultPersistenceManager(newFrozenTestConfig(testApp), engine, CachePolicy.NONE); + new DefaultPersistenceManager(engine, CachePolicy.NONE); CompoundWrite write = CompoundWrite.fromValue(fromSingleQuotedString("{'baz': 'new-baz', 'qux': 'qux'}")); diff --git a/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java b/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java index ce9a641db..58358a8ff 100644 --- a/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java +++ b/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java @@ -181,10 +181,10 @@ public void randomOperations() { DatabaseConfig cfg = TestHelpers.newFrozenTestConfig(testApp); MockPersistenceStorageEngine storageEngine = new MockPersistenceStorageEngine(); DefaultPersistenceManager manager = - new DefaultPersistenceManager(cfg, storageEngine, CachePolicy.NONE); + new DefaultPersistenceManager(storageEngine, CachePolicy.NONE); final HashMap tagMap = new HashMap<>(); SyncTree syncTree = - new SyncTree(cfg, manager, new SyncTree.ListenProvider() { + new SyncTree(manager, new SyncTree.ListenProvider() { @Override public void startListening( QuerySpec query, diff --git a/src/test/java/com/google/firebase/database/core/persistence/TrackedQueryManagerTest.java b/src/test/java/com/google/firebase/database/core/persistence/TrackedQueryManagerTest.java index 66b5a515f..8b293cedf 100644 --- a/src/test/java/com/google/firebase/database/core/persistence/TrackedQueryManagerTest.java +++ b/src/test/java/com/google/firebase/database/core/persistence/TrackedQueryManagerTest.java @@ -31,14 +31,12 @@ import com.google.firebase.database.core.utilities.TestClock; import com.google.firebase.database.core.view.QueryParams; import com.google.firebase.database.core.view.QuerySpec; -import com.google.firebase.database.logging.DefaultLogger; -import com.google.firebase.database.logging.LogWrapper; -import com.google.firebase.database.logging.Logger; import com.google.firebase.database.snapshot.ChildKey; import com.google.firebase.database.snapshot.PathIndex; import com.google.firebase.database.utilities.Clock; import java.util.Collections; import org.junit.Test; +import org.slf4j.LoggerFactory; public class TrackedQueryManagerTest { @@ -66,9 +64,8 @@ private TrackedQueryManager newManager(PersistenceStorageEngine engine, Clock cl e.disableTransactionCheck = true; engine = e; } - LogWrapper logWrapper = - new LogWrapper(new DefaultLogger(Logger.Level.WARN, null), TrackedQueryManagerTest.class); - return new TrackedQueryManager(engine, logWrapper, clock); + return new TrackedQueryManager( + engine, LoggerFactory.getLogger(TrackedQueryManagerTest.class), clock); } @Test diff --git a/src/test/java/com/google/firebase/database/integration/ShutdownExample.java b/src/test/java/com/google/firebase/database/integration/ShutdownExample.java index 68b6ac384..020e0416e 100644 --- a/src/test/java/com/google/firebase/database/integration/ShutdownExample.java +++ b/src/test/java/com/google/firebase/database/integration/ShutdownExample.java @@ -22,7 +22,6 @@ import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; -import com.google.firebase.database.Logger.Level; import com.google.firebase.database.ValueEventListener; import java.util.concurrent.Semaphore; @@ -38,7 +37,6 @@ public static void main(String[] args) { .build()); FirebaseDatabase db = FirebaseDatabase.getInstance(app); - db.setLogLevel(Level.DEBUG); DatabaseReference ref = db.getReference(); ValueEventListener listener = diff --git a/src/test/java/com/google/firebase/database/utilities/DefaultRunLoopTest.java b/src/test/java/com/google/firebase/database/utilities/DefaultRunLoopTest.java index 1029ebef8..065612568 100644 --- a/src/test/java/com/google/firebase/database/utilities/DefaultRunLoopTest.java +++ b/src/test/java/com/google/firebase/database/utilities/DefaultRunLoopTest.java @@ -38,7 +38,7 @@ public class DefaultRunLoopTest { public void testLifecycle() { MockRunLoop runLoop = new MockRunLoop(); try { - assertEquals(0, runLoop.getThreadPool().getCorePoolSize()); + assertEquals(1, runLoop.getThreadPool().getCorePoolSize()); runLoop.scheduleNow(new Runnable() { @Override public void run() { @@ -62,7 +62,7 @@ public void run() { public void testScheduleWithDelay() throws ExecutionException, InterruptedException { MockRunLoop runLoop = new MockRunLoop(); try { - assertEquals(0, runLoop.getThreadPool().getCorePoolSize()); + assertEquals(1, runLoop.getThreadPool().getCorePoolSize()); ScheduledFuture future = runLoop.schedule(new Runnable() { @Override public void run() { @@ -91,7 +91,7 @@ public void uncaughtException(Thread t, Throwable e) { assertSame(exceptionHandler, runLoop.getExceptionHandler()); try { - assertEquals(0, runLoop.getThreadPool().getCorePoolSize()); + assertEquals(1, runLoop.getThreadPool().getCorePoolSize()); runLoop.scheduleNow(new Runnable() { @Override public void run() { diff --git a/src/test/java/com/google/firebase/database/utilities/UtilitiesTest.java b/src/test/java/com/google/firebase/database/utilities/UtilitiesTest.java index 3af81179b..f4efbad57 100644 --- a/src/test/java/com/google/firebase/database/utilities/UtilitiesTest.java +++ b/src/test/java/com/google/firebase/database/utilities/UtilitiesTest.java @@ -25,13 +25,14 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.google.api.core.ApiFuture; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseException; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.DatabaseReference.CompletionListener; -import com.google.firebase.tasks.Task; +import java.util.concurrent.ExecutionException; import org.junit.Test; public class UtilitiesTest { @@ -142,37 +143,32 @@ public void testHardAssert() { } @Test - public void testWrapOnComplete() { - Pair, DatabaseReference.CompletionListener> result = + public void testWrapOnComplete() throws Exception { + Pair, DatabaseReference.CompletionListener> result = Utilities.wrapOnComplete(null); assertNotNull(result.getFirst()); assertNotNull(result.getSecond()); - try { - result.getFirst().getResult(); - fail("No error thrown for pending task"); - } catch (Exception e) { - // expected - } + assertFalse(result.getFirst().isDone()); result.getSecond().onComplete(null, null); - result.getFirst().getResult(); + assertTrue(result.getFirst().isDone()); + assertNull(result.getFirst().get()); } @Test - public void testWrapOnCompleteErrorResult() { - Pair, DatabaseReference.CompletionListener> result = + public void testWrapOnCompleteErrorResult() throws InterruptedException { + Pair, DatabaseReference.CompletionListener> result = Utilities.wrapOnComplete(null); assertNotNull(result.getFirst()); assertNotNull(result.getSecond()); - try { - result.getFirst().getResult(); - fail("No error thrown for pending task"); - } catch (Exception e) { - // expected - } + assertFalse(result.getFirst().isDone()); result.getSecond().onComplete(DatabaseError.fromStatus("test error"), null); - assertNotNull(result.getFirst().getException()); + try { + result.getFirst().get(); + } catch (ExecutionException e) { + assertNotNull(e.getCause()); + } } @Test @@ -183,7 +179,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { } }; - Pair, DatabaseReference.CompletionListener> result = + Pair, DatabaseReference.CompletionListener> result = Utilities.wrapOnComplete(listener); assertNull(result.getFirst()); assertSame(listener, result.getSecond()); diff --git a/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java b/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java index 549fb05b1..366bb7e29 100644 --- a/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java +++ b/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java @@ -27,12 +27,15 @@ import com.google.api.client.http.HttpResponseException; import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpResponse; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; +import com.google.firebase.testing.GenericFunction; import com.google.firebase.testing.TestResponseInterceptor; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import org.junit.After; @@ -60,7 +63,7 @@ public void testNoProjectId() { } @Test - public void testInvalidInstanceId() throws Exception { + public void testInvalidInstanceId() { FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(new MockGoogleCredentials("test-token")) .setProjectId("test-project") @@ -100,19 +103,38 @@ public void testDeleteInstanceId() throws Exception { .build(); FirebaseApp app = FirebaseApp.initializeApp(options); - FirebaseInstanceId instanceId = FirebaseInstanceId.getInstance(); + final FirebaseInstanceId instanceId = FirebaseInstanceId.getInstance(); assertSame(instanceId, FirebaseInstanceId.getInstance(app)); - TestResponseInterceptor interceptor = new TestResponseInterceptor(); - instanceId.setInterceptor(interceptor); - instanceId.deleteInstanceIdAsync("test-iid").get(); + List> functions = ImmutableList.of( + new GenericFunction() { + @Override + public Void call(Object... args) throws Exception { + instanceId.deleteInstanceIdAsync("test-iid").get(); + return null; + } + }, + new GenericFunction() { + @Override + public Void call(Object... args) throws Exception { + instanceId.deleteInstanceId("test-iid"); + return null; + } + } + ); - assertNotNull(interceptor.getResponse()); - HttpRequest request = interceptor.getResponse().getRequest(); - assertEquals("DELETE", request.getRequestMethod()); String url = "https://console.firebase.google.com/v1/project/test-project/instanceId/test-iid"; - assertEquals(url, request.getUrl().toString()); - assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); + for (GenericFunction fn : functions) { + TestResponseInterceptor interceptor = new TestResponseInterceptor(); + instanceId.setInterceptor(interceptor); + fn.call(); + + assertNotNull(interceptor.getResponse()); + HttpRequest request = interceptor.getResponse().getRequest(); + assertEquals("DELETE", request.getRequestMethod()); + assertEquals(url, request.getUrl().toString()); + assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); + } } @Test diff --git a/src/test/java/com/google/firebase/internal/CallableOperationTest.java b/src/test/java/com/google/firebase/internal/CallableOperationTest.java new file mode 100644 index 000000000..2ead9a9c7 --- /dev/null +++ b/src/test/java/com/google/firebase/internal/CallableOperationTest.java @@ -0,0 +1,106 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +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.auth.MockGoogleCredentials; +import com.google.firebase.internal.FirebaseThreadManagers.GlobalThreadManager; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import org.junit.After; +import org.junit.Test; + +public class CallableOperationTest { + + private static final String TEST_FIREBASE_THREAD = "test-firebase-thread"; + private static final FirebaseOptions OPTIONS = new Builder() + .setCredentials(new MockGoogleCredentials()) + .setThreadManager(new MockThreadManager()) + .build(); + + @After + public void tearDown() { + TestOnlyImplFirebaseTrampolines.clearInstancesForTest(); + } + + @Test + public void testCallResult() throws Exception { + FirebaseApp app = FirebaseApp.initializeApp(OPTIONS); + CallableOperation operation = new CallableOperation() { + @Override + protected Boolean execute() throws Exception { + String threadName = Thread.currentThread().getName(); + return TEST_FIREBASE_THREAD.equals(threadName); + } + }; + assertFalse(operation.call()); + assertTrue(operation.callAsync(app).get()); + } + + @Test + public void testCallException() throws Exception { + FirebaseApp app = FirebaseApp.initializeApp(OPTIONS); + CallableOperation operation = new CallableOperation() { + @Override + protected Boolean execute() throws Exception { + String threadName = Thread.currentThread().getName(); + if (TEST_FIREBASE_THREAD.equals(threadName)) { + throw new Exception(threadName); + } + return false; + } + }; + + assertFalse(operation.call()); + try { + operation.callAsync(app).get(); + fail("No exception thrown"); + } catch (ExecutionException e) { + assertEquals(TEST_FIREBASE_THREAD, e.getCause().getMessage()); + } + } + + private static class MockThreadManager extends GlobalThreadManager { + @Override + protected ExecutorService doInit() { + return Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() + .setNameFormat(TEST_FIREBASE_THREAD) + .build()); + } + + @Override + protected void doCleanup(ExecutorService executorService) { + executorService.shutdownNow(); + } + + @Override + protected ThreadFactory getThreadFactory() { + return Executors.defaultThreadFactory(); + } + } +} diff --git a/src/test/java/com/google/firebase/internal/FirebaseThreadManagersTest.java b/src/test/java/com/google/firebase/internal/FirebaseThreadManagersTest.java index 7a0d2d104..2b117f29f 100644 --- a/src/test/java/com/google/firebase/internal/FirebaseThreadManagersTest.java +++ b/src/test/java/com/google/firebase/internal/FirebaseThreadManagersTest.java @@ -29,7 +29,6 @@ import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; import com.google.firebase.internal.FirebaseThreadManagers.GlobalThreadManager; -import com.google.firebase.tasks.Tasks; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; @@ -38,7 +37,6 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadFactory; import org.junit.After; -import org.junit.Assume; import org.junit.Test; public class FirebaseThreadManagersTest { @@ -127,8 +125,6 @@ public void testGlobalThreadManagerReInit() { @Test public void testDefaultThreadManager() throws Exception { - Assume.assumeFalse(GaeThreadFactory.isAvailable()); - FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(new MockGoogleCredentials()) .build(); @@ -143,7 +139,7 @@ public Void call() throws Exception { return null; } }; - Tasks.await(ImplFirebaseTrampolines.submitCallable(defaultApp, command)); + ImplFirebaseTrampolines.submitCallable(defaultApp, command).get(); // Check for default JVM thread properties. assertTrue(threadInfo.get("name").toString().startsWith("firebase-default-")); diff --git a/src/test/java/com/google/firebase/internal/GaeExecutorServiceTest.java b/src/test/java/com/google/firebase/internal/GaeExecutorServiceTest.java deleted file mode 100644 index 48b2678cd..000000000 --- a/src/test/java/com/google/firebase/internal/GaeExecutorServiceTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.internal; - -import static com.google.firebase.database.TestHelpers.waitFor; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import com.google.common.collect.ImmutableList; -import com.google.firebase.testing.TestUtils; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.Semaphore; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.Test; - -public class GaeExecutorServiceTest { - - @Test - public void testShutdownBeforeUse() throws InterruptedException { - CountingThreadFactory threadFactory = new CountingThreadFactory(); - GaeExecutorService executorService = new GaeExecutorService("test", threadFactory); - assertFalse(executorService.isShutdown()); - assertFalse(executorService.isTerminated()); - - assertEquals(ImmutableList.of(), executorService.shutdownNow()); - assertTrue(executorService.isShutdown()); - assertTrue(executorService.isTerminated()); - assertTrue(executorService.awaitTermination(1, TimeUnit.SECONDS)); - assertEquals(0, threadFactory.counter.get()); - - executorService = new GaeExecutorService("test", threadFactory); - assertFalse(executorService.isShutdown()); - assertFalse(executorService.isTerminated()); - - executorService.shutdownNow(); - assertTrue(executorService.isShutdown()); - assertTrue(executorService.isTerminated()); - assertTrue(executorService.awaitTermination( - TestUtils.TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertEquals(0, threadFactory.counter.get()); - } - - @Test - public void testSubmit() throws InterruptedException, ExecutionException { - CountingThreadFactory threadFactory = new CountingThreadFactory(); - GaeExecutorService executorService = new GaeExecutorService("test", threadFactory); - - final Semaphore semaphore = new Semaphore(0); - Future future = executorService.submit(new Runnable() { - @Override - public void run() { - semaphore.release(); - } - }); - assertNotNull(future); - waitFor(semaphore); - - future = executorService.submit(new Runnable() { - @Override - public void run() { - semaphore.release(); - } - }, "result"); - assertNotNull(future); - waitFor(semaphore); - assertEquals("result", future.get()); - - future = executorService.submit(new Callable() { - @Override - public Object call() throws Exception { - semaphore.release(); - return "result2"; - } - }); - assertNotNull(future); - waitFor(semaphore); - assertEquals("result2", future.get()); - - executorService.execute(new Runnable() { - @Override - public void run() { - semaphore.release(); - } - }); - waitFor(semaphore); - - assertEquals(4, threadFactory.counter.get()); - - executorService.shutdown(); - assertTrue(executorService.awaitTermination( - TestUtils.TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); - assertTrue(executorService.isShutdown()); - assertTrue(executorService.isTerminated()); - } - - private static class CountingThreadFactory implements ThreadFactory { - - private final AtomicInteger counter = new AtomicInteger(0); - private final ThreadFactory delegate = Executors.defaultThreadFactory(); - - @Override - public Thread newThread(Runnable r) { - counter.incrementAndGet(); - return delegate.newThread(r); - } - } -} diff --git a/src/test/java/com/google/firebase/internal/RevivingScheduledExecutorTest.java b/src/test/java/com/google/firebase/internal/RevivingScheduledExecutorTest.java deleted file mode 100644 index 3a4da947c..000000000 --- a/src/test/java/com/google/firebase/internal/RevivingScheduledExecutorTest.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.internal; - -import java.lang.Thread.UncaughtExceptionHandler; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.Semaphore; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Assert; -import org.junit.Test; - -public class RevivingScheduledExecutorTest { - - private static final ThreadFactory THREAD_FACTORY = new ExceptionCatchingThreadFactory(); - - @Test - public void testAppEngineRunnable() throws InterruptedException { - final Semaphore semaphore = new Semaphore(0); - final Set threadIds = new HashSet<>(); - - RevivingScheduledExecutor executor = - new RevivingScheduledExecutor(THREAD_FACTORY, "testAppEngineRunnable", 0, 100); - - for (int i = 0; i < 50; ++i) { - // We delay the execution to give the cleanup handler a chance to run. Otherwise, the - // Executor's BlockingQueue will execute all Runnables before the internal thread gets - // replaced. - Thread.sleep(10); - executor.execute( - new Runnable() { - @Override - public void run() { - threadIds.add(Thread.currentThread().getId()); - semaphore.release(); - } - }); - } - - try { - Assert.assertTrue(semaphore.tryAcquire(50, 10, TimeUnit.SECONDS)); - Assert.assertTrue(threadIds.size() > 1); - } finally { - executor.shutdownNow(); - } - } - - @Test - public void testAppEnginePeriodicRunnable() throws InterruptedException { - final Set threadIds = new HashSet<>(); - final Semaphore semaphore = new Semaphore(0); - - RevivingScheduledExecutor executor = - new RevivingScheduledExecutor(THREAD_FACTORY, "testAppEnginePeriodicRunnable", 0, 100); - - ScheduledFuture future = - executor.scheduleAtFixedRate( - new Runnable() { - @Override - public void run() { - threadIds.add(Thread.currentThread().getId()); - semaphore.release(); - } - }, - 0, - 10, - TimeUnit.MILLISECONDS); - - try { - Assert.assertTrue(semaphore.tryAcquire(50, 10, TimeUnit.SECONDS)); - Assert.assertTrue(threadIds.size() > 1); - } finally { - future.cancel(true); - executor.shutdownNow(); - } - } - - @Test - public void testAppEngineDelayedRunnable() throws InterruptedException { - final Semaphore semaphore = new Semaphore(0); - final AtomicInteger threads = new AtomicInteger(0); - - RevivingScheduledExecutor executor = - new RevivingScheduledExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - threads.incrementAndGet(); - return THREAD_FACTORY.newThread(r); - } - }, - "testAppEngineDelayedRunnable", - 0, - 100); - - @SuppressWarnings("unused") - Future possiblyIgnoredError = - executor.schedule( - new Runnable() { - @Override - public void run() { - semaphore.release(); - } - }, - 750, - TimeUnit.MILLISECONDS); - - try { - Assert.assertFalse(semaphore.tryAcquire(1, 500, TimeUnit.MILLISECONDS)); - Assert.assertTrue(semaphore.tryAcquire(1, 500, TimeUnit.MILLISECONDS)); - Assert.assertTrue(threads.get() >= 2); - } finally { - executor.shutdownNow(); - } - } - - @Test - public void testAppEngineDelayedCallable() - throws InterruptedException, TimeoutException, ExecutionException { - final AtomicInteger threads = new AtomicInteger(0); - - RevivingScheduledExecutor executor = - new RevivingScheduledExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - threads.incrementAndGet(); - return THREAD_FACTORY.newThread(r); - } - }, - "testAppEngineDelayedCallable", - 0, - 100); - - ScheduledFuture future = - executor.schedule( - new Callable() { - @Override - public Boolean call() throws Exception { - return true; - } - }, - 750, - TimeUnit.MILLISECONDS); - - try { - Assert.assertTrue(future.get(1, TimeUnit.SECONDS)); - Assert.assertTrue(threads.get() >= 2); - } finally { - executor.shutdownNow(); - } - } - - @Test - public void testAppEngineCleanup() throws InterruptedException { - final Semaphore beforeSemaphore = new Semaphore(0); - final Semaphore afterSemaphore = new Semaphore(0); - final AtomicInteger threads = new AtomicInteger(0); - - RevivingScheduledExecutor executor = - new RevivingScheduledExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - threads.incrementAndGet(); - return THREAD_FACTORY.newThread(r); - } - }, - "testAppEngineCleanup", - 0, - 100) { - @Override - protected void beforeRestart() { - beforeSemaphore.release(); - } - - @Override - protected void afterRestart() { - afterSemaphore.release(); - } - }; - - @SuppressWarnings("unused") - Future possiblyIgnoredError = - executor.submit( - new Runnable() { - @Override - public void run() {} - }); - - try { - Assert.assertTrue(beforeSemaphore.tryAcquire(2, 10, TimeUnit.SECONDS)); - Assert.assertTrue(afterSemaphore.tryAcquire(2, 10, TimeUnit.SECONDS)); - Assert.assertEquals(3, threads.get()); - Assert.assertEquals(0, beforeSemaphore.availablePermits()); - Assert.assertEquals(0, afterSemaphore.availablePermits()); - } finally { - executor.shutdownNow(); - } - } - - private static class ExceptionCatchingThreadFactory implements ThreadFactory { - @Override - public Thread newThread(Runnable r) { - if (r == null) { - return null; - } - Thread thread = Executors.defaultThreadFactory().newThread(r); - thread.setUncaughtExceptionHandler( - new UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - // ignore -- to prevent the test output from getting cluttered - } - }); - return thread; - } - } -} diff --git a/src/test/java/com/google/firebase/internal/TaskToApiFutureTest.java b/src/test/java/com/google/firebase/internal/TaskToApiFutureTest.java deleted file mode 100644 index e5b64abb3..000000000 --- a/src/test/java/com/google/firebase/internal/TaskToApiFutureTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.internal; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import com.google.api.core.ApiFuture; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.firebase.tasks.Task; -import com.google.firebase.tasks.Tasks; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Test; - -public class TaskToApiFutureTest { - - @Test - public void testGetResult() throws Exception { - Task task = Tasks.forResult("test"); - ApiFuture future = new TaskToApiFuture<>(task); - assertEquals("test", future.get()); - assertFalse(future.isCancelled()); - assertTrue(future.isDone()); - } - - @Test - public void testGetError() throws Exception { - Task task = Tasks.forException(new RuntimeException("test")); - ApiFuture future = new TaskToApiFuture<>(task); - try { - future.get(); - } catch (ExecutionException e) { - assertEquals("test", e.getCause().getMessage()); - } - assertFalse(future.isCancelled()); - assertTrue(future.isDone()); - } - - @Test - public void testAddListener() throws Exception { - Task task = Tasks.forResult("test"); - ApiFuture future = new TaskToApiFuture<>(task); - final AtomicBoolean result = new AtomicBoolean(false); - future.addListener(new Runnable() { - @Override - public void run() { - result.set(true); - } - }, MoreExecutors.directExecutor()); - assertEquals("test", future.get()); - assertTrue(result.get()); - assertFalse(future.isCancelled()); - assertTrue(future.isDone()); - } - - @Test - public void testCancel() throws Exception { - Task task = Tasks.forResult("test"); - ApiFuture future = new TaskToApiFuture<>(task); - assertFalse(future.cancel(true)); - assertEquals("test", future.get()); - assertFalse(future.isCancelled()); - assertTrue(future.isDone()); - } -} diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java index d31668093..d36db08c0 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java @@ -35,6 +35,7 @@ import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; +import com.google.firebase.testing.GenericFunction; import com.google.firebase.testing.TestResponseInterceptor; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -115,18 +116,34 @@ public void testNullMessage() { public void testSend() throws Exception { MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() .setContent(MOCK_RESPONSE); - FirebaseMessaging messaging = initMessaging(response); + final FirebaseMessaging messaging = initMessaging(response); Map> testMessages = buildTestMessages(); - for (Map.Entry> entry : testMessages.entrySet()) { - response.setContent(MOCK_RESPONSE); - TestResponseInterceptor interceptor = new TestResponseInterceptor(); - messaging.setInterceptor(interceptor); - String resp = messaging.sendAsync(entry.getKey()).get(); - assertEquals("mock-name", resp); - - HttpRequest request = checkRequestHeader(interceptor); - checkRequest(request, ImmutableMap.of("message", entry.getValue())); + List> functions = ImmutableList.of( + new GenericFunction() { + @Override + public String call(Object... args) throws Exception { + return messaging.sendAsync((Message) args[0]).get(); + } + }, + new GenericFunction() { + @Override + public String call(Object... args) throws Exception { + return messaging.send((Message) args[0]); + } + } + ); + for (GenericFunction fn : functions) { + for (Map.Entry> entry : testMessages.entrySet()) { + response.setContent(MOCK_RESPONSE); + TestResponseInterceptor interceptor = new TestResponseInterceptor(); + messaging.setInterceptor(interceptor); + String resp = fn.call(entry.getKey()); + assertEquals("mock-name", resp); + + HttpRequest request = checkRequestHeader(interceptor); + checkRequest(request, ImmutableMap.of("message", entry.getValue())); + } } } @@ -134,18 +151,35 @@ public void testSend() throws Exception { public void testSendDryRun() throws Exception { MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() .setContent(MOCK_RESPONSE); - FirebaseMessaging messaging = initMessaging(response); + final FirebaseMessaging messaging = initMessaging(response); Map> testMessages = buildTestMessages(); - for (Map.Entry> entry : testMessages.entrySet()) { - response.setContent(MOCK_RESPONSE); - TestResponseInterceptor interceptor = new TestResponseInterceptor(); - messaging.setInterceptor(interceptor); - String resp = messaging.sendAsync(entry.getKey(), true).get(); - assertEquals("mock-name", resp); - - HttpRequest request = checkRequestHeader(interceptor); - checkRequest(request, ImmutableMap.of("message", entry.getValue(), "validate_only", true)); + List> functions = ImmutableList.of( + new GenericFunction() { + @Override + public String call(Object... args) throws Exception { + return messaging.sendAsync((Message) args[0], true).get(); + } + }, + new GenericFunction() { + @Override + public String call(Object... args) throws Exception { + return messaging.send((Message) args[0], true); + } + } + ); + + for (GenericFunction fn : functions) { + for (Map.Entry> entry : testMessages.entrySet()) { + response.setContent(MOCK_RESPONSE); + TestResponseInterceptor interceptor = new TestResponseInterceptor(); + messaging.setInterceptor(interceptor); + String resp = fn.call(entry.getKey()); + assertEquals("mock-name", resp); + + HttpRequest request = checkRequestHeader(interceptor); + checkRequest(request, ImmutableMap.of("message", entry.getValue(), "validate_only", true)); + } } } @@ -286,16 +320,41 @@ public void testInvalidSubscribe() { @Test public void testSubscribe() throws Exception { - MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() - .setContent("{\"results\": [{}, {\"error\": \"error_reason\"}]}"); - FirebaseMessaging messaging = initMessaging(response); - TestResponseInterceptor interceptor = new TestResponseInterceptor(); - messaging.setInterceptor(interceptor); - - TopicManagementResponse result = messaging.subscribeToTopicAsync( - ImmutableList.of("id1", "id2"), "test-topic").get(); - HttpRequest request = checkTopicManagementRequestHeader(interceptor, TEST_IID_SUBSCRIBE_URL); - checkTopicManagementRequest(request, result); + final String responseString = "{\"results\": [{}, {\"error\": \"error_reason\"}]}"; + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + final FirebaseMessaging messaging = initMessaging(response); + + List> functions = ImmutableList.of( + new GenericFunction() { + @Override + public TopicManagementResponse call(Object... args) throws Exception { + return messaging.subscribeToTopicAsync(ImmutableList.of("id1", "id2"), + "test-topic").get(); + } + }, + new GenericFunction() { + @Override + public TopicManagementResponse call(Object... args) throws Exception { + return messaging.subscribeToTopic(ImmutableList.of("id1", "id2"), "test-topic"); + } + }, + new GenericFunction() { + @Override + public TopicManagementResponse call(Object... args) throws Exception { + return messaging.subscribeToTopic(ImmutableList.of("id1", "id2"), + "/topics/test-topic"); + } + } + ); + + for (GenericFunction fn : functions) { + TestResponseInterceptor interceptor = new TestResponseInterceptor(); + messaging.setInterceptor(interceptor); + response.setContent(responseString); + TopicManagementResponse result = fn.call(); + HttpRequest request = checkTopicManagementRequestHeader(interceptor, TEST_IID_SUBSCRIBE_URL); + checkTopicManagementRequest(request, result); + } } @Test @@ -341,16 +400,42 @@ public void testInvalidUnsubscribe() { @Test public void testUnsubscribe() throws Exception { - MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() - .setContent("{\"results\": [{}, {\"error\": \"error_reason\"}]}"); - FirebaseMessaging messaging = initMessaging(response); - TestResponseInterceptor interceptor = new TestResponseInterceptor(); - messaging.setInterceptor(interceptor); - - TopicManagementResponse result = messaging.unsubscribeFromTopicAsync( - ImmutableList.of("id1", "id2"), "test-topic").get(); - HttpRequest request = checkTopicManagementRequestHeader(interceptor, TEST_IID_UNSUBSCRIBE_URL); - checkTopicManagementRequest(request, result); + final String responseString = "{\"results\": [{}, {\"error\": \"error_reason\"}]}"; + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + final FirebaseMessaging messaging = initMessaging(response); + + List> functions = ImmutableList.of( + new GenericFunction() { + @Override + public TopicManagementResponse call(Object... args) throws Exception { + return messaging.unsubscribeFromTopicAsync(ImmutableList.of("id1", "id2"), + "test-topic").get(); + } + }, + new GenericFunction() { + @Override + public TopicManagementResponse call(Object... args) throws Exception { + return messaging.unsubscribeFromTopic(ImmutableList.of("id1", "id2"), "test-topic"); + } + }, + new GenericFunction() { + @Override + public TopicManagementResponse call(Object... args) throws Exception { + return messaging.unsubscribeFromTopic(ImmutableList.of("id1", "id2"), + "/topics/test-topic"); + } + } + ); + + for (GenericFunction fn : functions) { + TestResponseInterceptor interceptor = new TestResponseInterceptor(); + messaging.setInterceptor(interceptor); + response.setContent(responseString); + TopicManagementResponse result = fn.call(); + HttpRequest request = checkTopicManagementRequestHeader( + interceptor, TEST_IID_UNSUBSCRIBE_URL); + checkTopicManagementRequest(request, result); + } } @Test diff --git a/src/test/java/com/google/firebase/tasks/OnCompleteCompletionListenerTest.java b/src/test/java/com/google/firebase/tasks/OnCompleteCompletionListenerTest.java deleted file mode 100644 index b080558ad..000000000 --- a/src/test/java/com/google/firebase/tasks/OnCompleteCompletionListenerTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import static org.mockito.Mockito.verifyZeroInteractions; - -import com.google.firebase.tasks.testing.TestOnCompleteListener; -import com.google.firebase.testing.MockitoTestRule; -import java.util.concurrent.Executor; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; - -public class OnCompleteCompletionListenerTest { - - @Rule public MockitoTestRule mockitoTestRule = new MockitoTestRule(); - - @Mock private Executor mockExecutor; - - @Test - public void testOnComplete_nothingExecutedAfterCancel() { - OnCompleteCompletionListener listener = - new OnCompleteCompletionListener<>(mockExecutor, new TestOnCompleteListener()); - listener.cancel(); - - TaskImpl task = new TaskImpl<>(); - task.setResult(null); - listener.onComplete(task); - - verifyZeroInteractions(mockExecutor); - } -} diff --git a/src/test/java/com/google/firebase/tasks/OnFailureCompletionListenerTest.java b/src/test/java/com/google/firebase/tasks/OnFailureCompletionListenerTest.java deleted file mode 100644 index 539a062cb..000000000 --- a/src/test/java/com/google/firebase/tasks/OnFailureCompletionListenerTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import static org.mockito.Mockito.verifyZeroInteractions; - -import com.google.firebase.tasks.testing.TestOnFailureListener; -import com.google.firebase.testing.MockitoTestRule; -import java.rmi.RemoteException; -import java.util.concurrent.Executor; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; - -public class OnFailureCompletionListenerTest { - - @Rule public MockitoTestRule mockitoTestRule = new MockitoTestRule(); - - @Mock private Executor mockExecutor; - - @Test - public void testOnComplete_nothingExecutedAfterCancel() { - OnFailureCompletionListener listener = - new OnFailureCompletionListener<>(mockExecutor, new TestOnFailureListener()); - listener.cancel(); - - TaskImpl task = new TaskImpl<>(); - task.setException(new RemoteException()); - listener.onComplete(task); - - verifyZeroInteractions(mockExecutor); - } - - @Test - public void testOnComplete_nothingExecutedOnSuccess() { - OnFailureCompletionListener listener = - new OnFailureCompletionListener<>(mockExecutor, new TestOnFailureListener()); - - TaskImpl task = new TaskImpl<>(); - task.setResult(null); - listener.onComplete(task); - - verifyZeroInteractions(mockExecutor); - } -} diff --git a/src/test/java/com/google/firebase/tasks/OnSuccessCompletionListenerTest.java b/src/test/java/com/google/firebase/tasks/OnSuccessCompletionListenerTest.java deleted file mode 100644 index 0fa73b1b9..000000000 --- a/src/test/java/com/google/firebase/tasks/OnSuccessCompletionListenerTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import static org.mockito.Mockito.verifyZeroInteractions; - -import com.google.firebase.tasks.testing.TestOnSuccessListener; -import com.google.firebase.testing.MockitoTestRule; -import java.rmi.RemoteException; -import java.util.concurrent.Executor; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; - -public class OnSuccessCompletionListenerTest { - - @Rule public MockitoTestRule mockitoTestRule = new MockitoTestRule(); - - @Mock private Executor mockExecutor; - - @Test - public void testOnComplete_nothingExecutedAfterCancel() { - OnSuccessCompletionListener listener = - new OnSuccessCompletionListener<>(mockExecutor, new TestOnSuccessListener<>()); - listener.cancel(); - - TaskImpl task = new TaskImpl<>(); - task.setResult(null); - listener.onComplete(task); - - verifyZeroInteractions(mockExecutor); - } - - @Test - public void testOnComplete_nothingExecutedOnFailure() { - OnSuccessCompletionListener listener = - new OnSuccessCompletionListener<>(mockExecutor, new TestOnSuccessListener<>()); - - TaskImpl task = new TaskImpl<>(); - task.setException(new RemoteException()); - listener.onComplete(task); - - verifyZeroInteractions(mockExecutor); - } -} diff --git a/src/test/java/com/google/firebase/tasks/TaskCompletionSourceTest.java b/src/test/java/com/google/firebase/tasks/TaskCompletionSourceTest.java deleted file mode 100644 index 54e4be36e..000000000 --- a/src/test/java/com/google/firebase/tasks/TaskCompletionSourceTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.rmi.RemoteException; -import org.junit.Test; - -public class TaskCompletionSourceTest { - - private static final String RESULT = "Success"; - private static final String RESULT_2 = "Success2"; - private static final RemoteException EXCEPTION = new RemoteException(); - private static final RemoteException EXCEPTION_2 = new RemoteException("2"); - - @Test - public void testSetResult() throws Exception { - TaskCompletionSource source = new TaskCompletionSource<>(); - Task task = source.getTask(); - - assertNotNull(task); - assertFalse(task.isComplete()); - - source.setResult(RESULT); - - assertTrue(task.isComplete()); - assertTrue(task.isSuccessful()); - assertEquals(RESULT, task.getResult()); - } - - @Test - public void testTrySetResult() throws Exception { - TaskCompletionSource source = new TaskCompletionSource<>(); - Task task = source.getTask(); - - assertTrue(source.trySetResult(RESULT)); - assertEquals(RESULT, task.getResult()); - } - - @Test - public void testTrySetResult_alreadySet() throws Exception { - TaskCompletionSource source = new TaskCompletionSource<>(); - Task task = source.getTask(); - - source.setResult(RESULT); - // Expect no exception here. - assertFalse(source.trySetResult(RESULT_2)); - assertEquals(RESULT, task.getResult()); - } - - @Test - public void testSetException() { - TaskCompletionSource source = new TaskCompletionSource<>(); - Task task = source.getTask(); - - assertNotNull(task); - assertFalse(task.isComplete()); - - source.setException(EXCEPTION); - - assertTrue(task.isComplete()); - assertFalse(task.isSuccessful()); - assertEquals(EXCEPTION, task.getException()); - } - - @Test - public void testTrySetException() { - TaskCompletionSource source = new TaskCompletionSource<>(); - Task task = source.getTask(); - - assertTrue(source.trySetException(EXCEPTION)); - assertEquals(EXCEPTION, task.getException()); - } - - @Test - public void testTrySetException_alreadySet() { - TaskCompletionSource source = new TaskCompletionSource<>(); - Task task = source.getTask(); - - source.setException(EXCEPTION); - // Expect no exception here. - assertFalse(source.trySetException(EXCEPTION_2)); - assertEquals(EXCEPTION, task.getException()); - } -} diff --git a/src/test/java/com/google/firebase/tasks/TaskExecutorsTest.java b/src/test/java/com/google/firebase/tasks/TaskExecutorsTest.java deleted file mode 100644 index 57e7f04b2..000000000 --- a/src/test/java/com/google/firebase/tasks/TaskExecutorsTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.TimeUnit; -import org.junit.Assert; -import org.junit.Test; - -public class TaskExecutorsTest { - - private static final long TIMEOUT_MS = 500; - - @Test - public void testDefaultThreadPool() throws InterruptedException { - final ArrayBlockingQueue sync = new ArrayBlockingQueue<>(1); - TaskExecutors.DEFAULT_THREAD_POOL.execute( - new Runnable() { - @Override - public void run() { - sync.add(Thread.currentThread()); - } - }); - Thread actual = sync.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS); - Assert.assertNotEquals(Thread.currentThread(), actual); - } - - @Test - public void testDirect() throws InterruptedException { - final ArrayBlockingQueue sync = new ArrayBlockingQueue<>(1); - TaskExecutors.DIRECT.execute( - new Runnable() { - @Override - public void run() { - sync.add(Thread.currentThread()); - } - }); - Thread actual = sync.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS); - Assert.assertEquals(Thread.currentThread(), actual); - } -} diff --git a/src/test/java/com/google/firebase/tasks/TaskImplTest.java b/src/test/java/com/google/firebase/tasks/TaskImplTest.java deleted file mode 100644 index 8a3a65c25..000000000 --- a/src/test/java/com/google/firebase/tasks/TaskImplTest.java +++ /dev/null @@ -1,600 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import com.google.firebase.internal.NonNull; -import com.google.firebase.tasks.testing.TestOnCompleteListener; -import com.google.firebase.tasks.testing.TestOnFailureListener; -import com.google.firebase.tasks.testing.TestOnSuccessListener; -import java.rmi.RemoteException; -import org.junit.Test; - -public class TaskImplTest { - - private static final Exception EXCEPTION = new RemoteException(); - private static final Void NULL_RESULT = null; - private static final String NON_NULL_RESULT = "Success"; - - @Test - public void testIsComplete_notComplete() { - TaskImpl task = new TaskImpl<>(); - assertFalse(task.isComplete()); - } - - @Test - public void testIsComplete_failed() { - TaskImpl task = new TaskImpl<>(); - task.setException(EXCEPTION); - assertTrue(task.isComplete()); - } - - @Test - public void testIsComplete_nullResult() { - TaskImpl task = new TaskImpl<>(); - task.setResult(NULL_RESULT); - assertTrue(task.isComplete()); - } - - @Test - public void testIsComplete_nonNullResult() { - TaskImpl task = new TaskImpl<>(); - task.setResult(NON_NULL_RESULT); - assertTrue(task.isComplete()); - } - - @Test - public void testIsSuccessful_notComplete() { - TaskImpl task = new TaskImpl<>(); - assertFalse(task.isSuccessful()); - } - - @Test - public void testIsSuccessful_failed() { - TaskImpl task = new TaskImpl<>(); - task.setException(EXCEPTION); - assertFalse(task.isSuccessful()); - } - - @Test - public void testIsSuccessful_nullResult() { - TaskImpl task = new TaskImpl<>(); - task.setResult(NULL_RESULT); - assertTrue(task.isSuccessful()); - } - - @Test - public void testIsSuccessful_nonNullResult() { - TaskImpl task = new TaskImpl<>(); - task.setResult(NON_NULL_RESULT); - assertTrue(task.isSuccessful()); - } - - @Test(expected = IllegalStateException.class) - public void testGetResult_notComplete() { - TaskImpl task = new TaskImpl<>(); - task.getResult(); - } - - @Test - public void testGetResult_nullResult() { - TaskImpl task = new TaskImpl<>(); - task.setResult(NULL_RESULT); - assertNull(task.getResult()); - } - - @Test - public void testGetResult_nonNullResult() { - TaskImpl task = new TaskImpl<>(); - task.setResult(NON_NULL_RESULT); - assertEquals(NON_NULL_RESULT, task.getResult()); - } - - @Test - public void testTrySetResult_nullResult() { - TaskImpl task = new TaskImpl<>(); - assertTrue(task.trySetResult(NULL_RESULT)); - assertNull(task.getResult()); - } - - @Test - public void testTrySetResult_nonNullResult() { - TaskImpl task = new TaskImpl<>(); - assertTrue(task.trySetResult(NON_NULL_RESULT)); - assertEquals(NON_NULL_RESULT, task.getResult()); - } - - @Test - public void testGetResult_failure() { - TaskImpl task = new TaskImpl<>(); - task.setException(EXCEPTION); - - try { - task.getResult(); - fail("No exception thrown"); - } catch (RuntimeExecutionException e) { - assertSame(EXCEPTION, e.getCause()); - } - } - - @Test - public void testGetResult_exceptionIsSpecifiedType() throws Exception { - TaskImpl task = new TaskImpl<>(); - task.setException(EXCEPTION); - - try { - task.getResult(RemoteException.class); - fail("No exception thrown"); - } catch (RemoteException e) { - assertSame(EXCEPTION, e); - } - } - - @Test - public void testGetResult_exceptionIsNotSpecifiedType() throws Exception { - TaskImpl task = new TaskImpl<>(); - Exception exception = new RuntimeException(); - task.setException(exception); - - try { - task.getResult(RemoteException.class); - fail("No exception thrown"); - } catch (RuntimeExecutionException e) { - assertSame(exception, e.getCause()); - } - } - - @Test - public void testGetException_notComplete() { - TaskImpl task = new TaskImpl<>(); - assertNull(task.getException()); - } - - @Test - public void testGetException_failure() { - TaskImpl task = new TaskImpl<>(); - task.setException(EXCEPTION); - assertEquals(EXCEPTION, task.getException()); - } - - @Test - public void testTrySetException() { - TaskImpl task = new TaskImpl<>(); - assertTrue(task.trySetException(EXCEPTION)); - assertEquals(EXCEPTION, task.getException()); - } - - @Test - public void testGetException_nullResult() { - TaskImpl task = new TaskImpl<>(); - task.setResult(NULL_RESULT); - assertNull(task.getException()); - } - - @Test - public void testGetException_nonNullResult() { - TaskImpl task = new TaskImpl<>(); - task.setResult(NON_NULL_RESULT); - assertNull(task.getException()); - } - - @Test - public void testOnSuccess_nullResult() throws Exception { - TaskImpl task = new TaskImpl<>(); - TestOnSuccessListener listener = new TestOnSuccessListener<>(); - task.addOnSuccessListener(listener); - - task.setResult(NULL_RESULT); - - assertTrue(listener.await()); - assertNull(listener.getResult()); - } - - @Test - public void testOnSuccess_nonNullResult() throws Exception { - TaskImpl task = new TaskImpl<>(); - TestOnSuccessListener listener = new TestOnSuccessListener<>(); - task.addOnSuccessListener(listener); - - task.setResult(NON_NULL_RESULT); - - assertTrue(listener.await()); - assertEquals(NON_NULL_RESULT, listener.getResult()); - } - - @Test - public void testOnSuccess_alreadyComplete() throws Exception { - TaskImpl task = new TaskImpl<>(); - TestOnSuccessListener listener = new TestOnSuccessListener<>(); - task.setResult(NON_NULL_RESULT); - - task.addOnSuccessListener(listener); - - assertTrue(listener.await()); - assertEquals(NON_NULL_RESULT, listener.getResult()); - } - - @Test - public void testOnSuccess_acceptsBaseResult() throws Exception { - TaskImpl task = new TaskImpl<>(); - TestOnSuccessListener listener = new TestOnSuccessListener<>(); - task.setResult(NON_NULL_RESULT); - - task.addOnSuccessListener(listener); - - assertTrue(listener.await()); - assertEquals(NON_NULL_RESULT, listener.getResult()); - } - - @Test - public void testOnFailure() throws Exception { - TaskImpl task = new TaskImpl<>(); - TestOnFailureListener listener = new TestOnFailureListener(); - task.addOnFailureListener(listener); - - task.setException(EXCEPTION); - - assertTrue(listener.await()); - assertEquals(EXCEPTION, listener.getException()); - } - - @Test - public void testOnFailure_alreadyComplete() throws Exception { - TaskImpl task = new TaskImpl<>(); - TestOnFailureListener listener = new TestOnFailureListener(); - task.setException(EXCEPTION); - - task.addOnFailureListener(listener); - - assertTrue(listener.await()); - assertEquals(EXCEPTION, listener.getException()); - } - - @Test - public void testOnComplete_nullResult() throws Exception { - TaskImpl task = new TaskImpl<>(); - TestOnCompleteListener listener = new TestOnCompleteListener<>(); - task.addOnCompleteListener(listener); - - task.setResult(NULL_RESULT); - - assertTrue(listener.await()); - assertEquals(task, listener.getTask()); - } - - @Test - public void testOnComplete_nonNullResult() throws Exception { - TaskImpl task = new TaskImpl<>(); - TestOnCompleteListener listener = new TestOnCompleteListener<>(); - task.addOnCompleteListener(listener); - - task.setResult(NON_NULL_RESULT); - - assertTrue(listener.await()); - assertEquals(task, listener.getTask()); - } - - @Test - public void testOnComplete_failure() throws Exception { - TaskImpl task = new TaskImpl<>(); - TestOnCompleteListener listener = new TestOnCompleteListener<>(); - task.addOnCompleteListener(listener); - - task.setException(EXCEPTION); - - assertTrue(listener.await()); - assertEquals(task, listener.getTask()); - } - - @Test - public void testOnComplete_alreadySucceeded() throws Exception { - TaskImpl task = new TaskImpl<>(); - task.setResult(NULL_RESULT); - - TestOnCompleteListener listener = new TestOnCompleteListener<>(); - task.addOnCompleteListener(listener); - - assertTrue(listener.await()); - assertEquals(task, listener.getTask()); - } - - @Test - public void testOnComplete_alreadyFailed() throws Exception { - TaskImpl task = new TaskImpl<>(); - task.setException(EXCEPTION); - - TestOnCompleteListener listener = new TestOnCompleteListener<>(); - task.addOnCompleteListener(listener); - - assertTrue(listener.await()); - assertEquals(task, listener.getTask()); - } - - @Test - public void testContinueWith() { - TaskImpl task = new TaskImpl<>(); - Task task2 = - task.continueWith( - TaskExecutors.DIRECT, - new Continuation() { - @Override - public String then(@NonNull Task task) throws Exception { - assertNull(task.getResult()); - return NON_NULL_RESULT; - } - }); - task.setResult(null); - assertEquals(NON_NULL_RESULT, task2.getResult()); - } - - @Test - public void testContinueWith_alreadyComplete() { - TaskImpl task = new TaskImpl<>(); - task.setResult(null); - Task task2 = - task.continueWith( - TaskExecutors.DIRECT, - new Continuation() { - @Override - public Object then(@NonNull Task task) throws Exception { - assertNull(task.getResult()); - return NON_NULL_RESULT; - } - }); - assertEquals(NON_NULL_RESULT, task2.getResult()); - } - - @Test - public void testContinueWith_propagatesException() { - TaskImpl task = new TaskImpl<>(); - Task task2 = - task.continueWith( - TaskExecutors.DIRECT, - new Continuation() { - @Override - public Void then(@NonNull Task task) throws Exception { - task.getResult(); - throw new AssertionError("Expected getResult to throw"); - } - }); - task.setException(EXCEPTION); - assertEquals(EXCEPTION, task2.getException()); - } - - @Test - public void testContinueWith_continuationThrows() { - TaskImpl task = new TaskImpl<>(); - Task task2 = - task.continueWith( - TaskExecutors.DIRECT, - new Continuation() { - @Override - public Void then(@NonNull Task task) throws Exception { - throw EXCEPTION; - } - }); - task.setResult(null); - assertEquals(EXCEPTION, task2.getException()); - } - - @Test - public void testContinueWith_continuationThrowsWrapperWithoutCause() { - TaskImpl task = new TaskImpl<>(); - Task task2 = - task.continueWith( - TaskExecutors.DIRECT, - new Continuation() { - @Override - public Void then(@NonNull Task task) throws Exception { - throw new RuntimeExecutionException(null); - } - }); - task.setResult(null); - assertTrue(task2.getException() instanceof RuntimeExecutionException); - } - - @Test - public void testContinueWith_continuationReturnsNull() { - TaskImpl task = new TaskImpl<>(); - Task task2 = - task.continueWith( - TaskExecutors.DIRECT, - new Continuation() { - @Override - public Void then(@NonNull Task task) throws Exception { - assertEquals(NON_NULL_RESULT, task.getResult()); - return null; - } - }); - task.setResult(NON_NULL_RESULT); - assertNull(task2.getResult()); - } - - @Test - public void testContinueWithTask() { - TaskImpl task = new TaskImpl<>(); - Task task2 = - task.continueWithTask( - TaskExecutors.DIRECT, - new Continuation>() { - @Override - public Task then(@NonNull Task task) throws Exception { - assertNull(task.getResult()); - return Tasks.forResult(NON_NULL_RESULT); - } - }); - task.setResult(null); - assertEquals(NON_NULL_RESULT, task2.getResult()); - } - - @Test - public void testContinueWithTask_alreadyComplete() { - TaskImpl task = new TaskImpl<>(); - task.setResult(null); - Task task2 = - task.continueWithTask( - TaskExecutors.DIRECT, - new Continuation>() { - @Override - public Task then(@NonNull Task task) throws Exception { - assertNull(task.getResult()); - return Tasks.forResult(NON_NULL_RESULT); - } - }); - assertEquals(NON_NULL_RESULT, task2.getResult()); - } - - @Test - public void testContinueWithTask_propagatesException() { - TaskImpl task = new TaskImpl<>(); - Task task2 = - task.continueWithTask( - TaskExecutors.DIRECT, - new Continuation>() { - @Override - public Task then(@NonNull Task task) throws Exception { - task.getResult(); - throw new AssertionError("Expected getResult to throw"); - } - }); - task.setException(EXCEPTION); - assertEquals(EXCEPTION, task2.getException()); - } - - @Test - public void testContinueWithTask_continuationThrows() { - TaskImpl task = new TaskImpl<>(); - Task task2 = - task.continueWithTask( - TaskExecutors.DIRECT, - new Continuation>() { - @Override - public Task then(@NonNull Task task) throws Exception { - throw EXCEPTION; - } - }); - task.setResult(null); - assertEquals(EXCEPTION, task2.getException()); - } - - @Test - public void testContinueWithTask_continuationThrowsWrapperWithoutCause() { - TaskImpl task = new TaskImpl<>(); - Task task2 = - task.continueWithTask( - TaskExecutors.DIRECT, - new Continuation>() { - @Override - public Task then(@NonNull Task task) throws Exception { - throw new RuntimeExecutionException(null); - } - }); - task.setResult(null); - assertTrue(task2.getException() instanceof RuntimeExecutionException); - } - - @Test - public void testContinueWithTask_continuationReturnsIncompleteTask() { - TaskImpl task = new TaskImpl<>(); - final TaskImpl task2 = new TaskImpl<>(); - Task task3 = - task.continueWithTask( - TaskExecutors.DIRECT, - new Continuation>() { - @Override - public Task then(@NonNull Task task) throws Exception { - return task2; - } - }); - task.setResult(NULL_RESULT); - assertFalse(task3.isComplete()); - - task2.setResult(NON_NULL_RESULT); - assertEquals(NON_NULL_RESULT, task3.getResult()); - } - - @Test - public void testContinueWithTask_continuationReturnsOriginalTask() { - TaskImpl task = new TaskImpl<>(); - Task task2 = - task.continueWithTask( - TaskExecutors.DIRECT, - new Continuation>() { - @Override - public Task then(@NonNull Task task) throws Exception { - return task; - } - }); - task.setResult(NON_NULL_RESULT); - assertEquals(NON_NULL_RESULT, task2.getResult()); - } - - @Test - public void testContinueWithTask_continuationReturnsNull() { - TaskImpl task = new TaskImpl<>(); - Task task2 = - task.continueWithTask( - TaskExecutors.DIRECT, - new Continuation>() { - @Override - public Task then(@NonNull Task task) throws Exception { - assertEquals(NON_NULL_RESULT, task.getResult()); - return null; - } - }); - task.setResult(NON_NULL_RESULT); - assertTrue(task2.getException() instanceof NullPointerException); - } - - @Test(expected = IllegalStateException.class) - public void testSetResult_alreadyComplete() { - TaskImpl task = new TaskImpl<>(); - task.setException(EXCEPTION); - task.setResult(NULL_RESULT); - } - - @Test - public void testTrySetResult_alreadyComplete() { - TaskImpl task = new TaskImpl<>(); - task.setException(EXCEPTION); - // Expect no exception to be thrown. - assertFalse(task.trySetResult(NULL_RESULT)); - assertEquals(EXCEPTION, task.getException()); - } - - @Test(expected = IllegalStateException.class) - public void testSetException_alreadyComplete() { - TaskImpl task = new TaskImpl<>(); - task.setResult(NULL_RESULT); - task.setException(EXCEPTION); - } - - @Test - public void testTrySetException_alreadyComplete() { - TaskImpl task = new TaskImpl<>(); - task.setResult(NULL_RESULT); - // Expect no exception to be thrown. - assertFalse(task.trySetException(EXCEPTION)); - assertNull(task.getResult()); - } -} diff --git a/src/test/java/com/google/firebase/tasks/TasksTest.java b/src/test/java/com/google/firebase/tasks/TasksTest.java deleted file mode 100644 index 067ed93c2..000000000 --- a/src/test/java/com/google/firebase/tasks/TasksTest.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.rmi.RemoteException; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import org.junit.Test; - -public class TasksTest { - - private static final Object RESULT = new Object(); - private static final RemoteException EXCEPTION = new RemoteException(); - private static final int SCHEDULE_DELAY_MS = 50; - private static final int TIMEOUT_MS = 200; - private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - - @Test - public void testForResult() throws Exception { - Task task = Tasks.forResult(RESULT); - assertEquals(RESULT, task.getResult()); - } - - @Test - public void testForResult_nullResult() throws Exception { - Task task = Tasks.forResult(null); - assertNull(task.getResult()); - } - - @Test - public void testForException() { - Task task = Tasks.forException(EXCEPTION); - assertEquals(EXCEPTION, task.getException()); - } - - @Test(expected = NullPointerException.class) - @SuppressWarnings("ConstantConditions") - public void testForException_nullException() { - Tasks.forException(null); - } - - @Test - public void testCall_nonNullResult() { - Task task = - Tasks.call( - TaskExecutors.DIRECT, - new Callable() { - @Override - public Object call() throws Exception { - return RESULT; - } - }); - assertEquals(RESULT, task.getResult()); - } - - @Test - public void testCall_nullResult() { - Task task = - Tasks.call( - TaskExecutors.DIRECT, - new Callable() { - @Override - public Void call() throws Exception { - return null; - } - }); - assertNull(task.getResult()); - } - - @Test - public void testCall_exception() { - Task task = - Tasks.call( - TaskExecutors.DIRECT, - new Callable() { - @Override - public Void call() throws Exception { - throw EXCEPTION; - } - }); - assertEquals(EXCEPTION, task.getException()); - } - - @Test(expected = NullPointerException.class) - public void testCall_nullCallable() { - Tasks.call(null); - } - - @Test(expected = NullPointerException.class) - public void testCall_nullExecutor() { - Tasks.call( - null, - new Callable() { - @Override - public Void call() throws Exception { - return null; - } - }); - } - - @Test - public void testAwait() throws Exception { - TaskCompletionSource completionSource = new TaskCompletionSource<>(); - scheduleResult(completionSource); - assertEquals( - RESULT, Tasks.await(completionSource.getTask(), TIMEOUT_MS, TimeUnit.MILLISECONDS)); - } - - @Test - public void testAwait_noTimeout() throws Exception { - TaskCompletionSource completionSource = new TaskCompletionSource<>(); - scheduleResult(completionSource); - assertEquals(RESULT, Tasks.await(completionSource.getTask())); - } - - @Test - public void testAwait_exception() throws Exception { - TaskCompletionSource completionSource = new TaskCompletionSource<>(); - scheduleException(completionSource); - - try { - Tasks.await(completionSource.getTask(), TIMEOUT_MS, TimeUnit.MILLISECONDS); - fail("No exception thrown"); - } catch (ExecutionException e) { - assertSame(EXCEPTION, e.getCause()); - } - } - - @Test - public void testAwait_noTimeoutException() throws Exception { - TaskCompletionSource completionSource = new TaskCompletionSource<>(); - scheduleException(completionSource); - - try { - Tasks.await(completionSource.getTask()); - fail("No exception thrown"); - } catch (ExecutionException e) { - assertSame(EXCEPTION, e.getCause()); - } - } - - @Test - public void testAwait_alreadyFailed() throws Exception { - Task task = Tasks.forException(EXCEPTION); - - try { - Tasks.await(task, TIMEOUT_MS, TimeUnit.MILLISECONDS); - fail("No exception thrown"); - } catch (ExecutionException e) { - assertSame(EXCEPTION, e.getCause()); - } - } - - @Test - public void testAwait_noTimeoutAlreadyFailed() throws Exception { - Task task = Tasks.forException(EXCEPTION); - - try { - Tasks.await(task); - fail("No exception thrown"); - } catch (ExecutionException e) { - assertSame(EXCEPTION, e.getCause()); - } - } - - @Test - public void testAwait_alreadySucceeded() throws Exception { - Task task = Tasks.forResult(RESULT); - assertEquals(RESULT, Tasks.await(task, TIMEOUT_MS, TimeUnit.MILLISECONDS)); - } - - @Test - public void testAwait_noTimeoutAlreadySucceeded() throws Exception { - Task task = Tasks.forResult(RESULT); - assertEquals(RESULT, Tasks.await(task)); - } - - @Test(expected = InterruptedException.class) - public void testAwait_interrupted() throws Exception { - Task task = new TaskImpl<>(); - scheduleInterrupt(); - Tasks.await(task, TIMEOUT_MS, TimeUnit.MILLISECONDS); - } - - @Test(expected = InterruptedException.class) - public void testAwait_noTimeoutInterrupted() throws Exception { - Task task = new TaskImpl<>(); - scheduleInterrupt(); - Tasks.await(task); - } - - @Test(expected = TimeoutException.class) - public void testAwait_timeout() throws Exception { - TaskImpl task = new TaskImpl<>(); - Tasks.await(task, TIMEOUT_MS, TimeUnit.MILLISECONDS); - } - - @Test(expected = TimeoutException.class) - public void testWhenAll_notCompleted() throws Exception { - Task task1 = new TaskImpl<>(); - Task task2 = new TaskImpl<>(); - Task task = Tasks.whenAll(task1, task2); - Tasks.await(task, TIMEOUT_MS, TimeUnit.MILLISECONDS); - } - - @Test(expected = TimeoutException.class) - public void testWhenAll_partiallyCompleted() throws Exception { - Task task1 = Tasks.forResult(RESULT); - Task task2 = new TaskImpl<>(); - Task task = Tasks.whenAll(task1, task2); - Tasks.await(task, TIMEOUT_MS, TimeUnit.MILLISECONDS); - } - - @Test - public void testWhenAll_completedFailure() throws Exception { - Task task1 = Tasks.forResult(RESULT); - Task task2 = Tasks.forException(EXCEPTION); - Task task = Tasks.whenAll(task1, task2); - - try { - Tasks.await(task); - fail("No exception thrown"); - } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof ExecutionException); - } - } - - @Test - public void testWhenAll_completedSuccess() throws Exception { - Task task1 = Tasks.forResult(RESULT); - Task task2 = Tasks.forResult(RESULT); - Task task = Tasks.whenAll(task1, task2); - assertNull(Tasks.await(task)); - } - - @Test - public void testWhenAll_completedEmpty() throws Exception { - Task task = Tasks.whenAll(); - assertNull(Tasks.await(task)); - } - - @Test(expected = NullPointerException.class) - public void testWhenAll_nullOnInput() throws Exception { - Task task = Tasks.forResult(RESULT); - Tasks.whenAll(task, null, task); - } - - private void scheduleResult(final TaskCompletionSource completionSource) { - @SuppressWarnings("unused") - Future possiblyIgnoredError = - executor.schedule( - new Runnable() { - @Override - public void run() { - completionSource.setResult(RESULT); - } - }, - SCHEDULE_DELAY_MS, - TimeUnit.MILLISECONDS); - } - - private void scheduleException(final TaskCompletionSource completionSource) { - @SuppressWarnings("unused") - Future possiblyIgnoredError = - executor.schedule( - new Runnable() { - @Override - public void run() { - completionSource.setException(EXCEPTION); - } - }, - SCHEDULE_DELAY_MS, - TimeUnit.MILLISECONDS); - } - - private void scheduleInterrupt() { - final Thread testThread = Thread.currentThread(); - @SuppressWarnings("unused") - Future possiblyIgnoredError = - executor.schedule( - new Runnable() { - @Override - public void run() { - testThread.interrupt(); - } - }, - SCHEDULE_DELAY_MS, - TimeUnit.MILLISECONDS); - } -} diff --git a/src/test/java/com/google/firebase/tasks/testing/TestOnCompleteListener.java b/src/test/java/com/google/firebase/tasks/testing/TestOnCompleteListener.java deleted file mode 100644 index 3466597b4..000000000 --- a/src/test/java/com/google/firebase/tasks/testing/TestOnCompleteListener.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks.testing; - -import static com.google.common.base.Preconditions.checkState; - -import com.google.firebase.internal.NonNull; -import com.google.firebase.tasks.OnCompleteListener; -import com.google.firebase.tasks.Task; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * Implementation of {@link OnCompleteListener} for use in tests. - */ -public class TestOnCompleteListener implements OnCompleteListener { - - private static final long TIMEOUT_MS = 500; - - private final CountDownLatch latch = new CountDownLatch(1); - private Task task; - private Thread thread; - - @Override - public void onComplete(@NonNull Task task) { - this.task = task; - thread = Thread.currentThread(); - latch.countDown(); - } - - /** - * Blocks until the {@link #onComplete} is called. - */ - public boolean await() throws InterruptedException { - return latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); - } - - /** - * Returns the Task passed to {@link #onComplete}. - */ - public Task getTask() { - checkState(latch.getCount() == 0, "onComplete has not been called"); - return task; - } - - /** - * Returns the Thread that {@link #onComplete} was called on. - */ - public Thread getThread() { - checkState(latch.getCount() == 0, "onFailure has not been called"); - return thread; - } -} diff --git a/src/test/java/com/google/firebase/tasks/testing/TestOnFailureListener.java b/src/test/java/com/google/firebase/tasks/testing/TestOnFailureListener.java deleted file mode 100644 index ba436a0c7..000000000 --- a/src/test/java/com/google/firebase/tasks/testing/TestOnFailureListener.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks.testing; - -import static com.google.common.base.Preconditions.checkState; - -import com.google.firebase.internal.NonNull; -import com.google.firebase.tasks.OnFailureListener; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * Implementation of {@link OnFailureListener} for use in tests. - */ -public class TestOnFailureListener implements OnFailureListener { - - private static final long TIMEOUT_MS = 500; - - private final CountDownLatch latch = new CountDownLatch(1); - private Exception exception; - private Thread thread; - - @Override - public void onFailure(@NonNull Exception e) { - exception = e; - thread = Thread.currentThread(); - latch.countDown(); - } - - /** - * Blocks until the {@link #onFailure} is called. - */ - public boolean await() throws InterruptedException { - return latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); - } - - /** - * Returns the exception passed to {@link #onFailure}. - */ - public Exception getException() { - checkState(latch.getCount() == 0, "onFailure has not been called"); - return exception; - } - - /** - * Returns the Thread that {@link #onFailure} was called on. - */ - public Thread getThread() { - checkState(latch.getCount() == 0, "onFailure has not been called"); - return thread; - } -} diff --git a/src/test/java/com/google/firebase/tasks/testing/TestOnSuccessListener.java b/src/test/java/com/google/firebase/tasks/testing/TestOnSuccessListener.java deleted file mode 100644 index 71ba3ffb9..000000000 --- a/src/test/java/com/google/firebase/tasks/testing/TestOnSuccessListener.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.firebase.tasks.testing; - -import static com.google.common.base.Preconditions.checkState; - -import com.google.firebase.tasks.OnSuccessListener; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * Implementation of {@link OnSuccessListener} for use in tests. - */ -public class TestOnSuccessListener implements OnSuccessListener { - - private static final long TIMEOUT_MS = 500; - - private final CountDownLatch latch = new CountDownLatch(1); - private T result; - private Thread thread; - - @Override - public void onSuccess(T result) { - this.result = result; - this.thread = Thread.currentThread(); - latch.countDown(); - } - - /** - * Blocks until the {@link #onSuccess} is called. - */ - public boolean await() throws InterruptedException { - return latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); - } - - /** - * Returns the result passed to {@link #onSuccess}. - */ - public T getResult() { - checkState(latch.getCount() == 0, "onSuccess has not been called"); - return result; - } - - /** - * Returns the Thread that {@link #onSuccess} was called on. - */ - public Thread getThread() { - checkState(latch.getCount() == 0, "onSuccess has not been called"); - return thread; - } -} diff --git a/src/main/java/com/google/firebase/database/logging/package-info.java b/src/test/java/com/google/firebase/testing/GenericFunction.java similarity index 75% rename from src/main/java/com/google/firebase/database/logging/package-info.java rename to src/test/java/com/google/firebase/testing/GenericFunction.java index 37bc5cf37..b7dc8a84a 100644 --- a/src/main/java/com/google/firebase/database/logging/package-info.java +++ b/src/test/java/com/google/firebase/testing/GenericFunction.java @@ -14,7 +14,13 @@ * limitations under the License. */ +package com.google.firebase.testing; + /** - * @hide + * A generic operation that can be called with arbitrary arguments. */ -package com.google.firebase.database.logging; +public interface GenericFunction { + + T call(Object ...args) throws Exception; + +}