/* * Copyright 2018-present MongoDB, 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.mongodb.stitch.android.core.internal; import com.google.android.gms.tasks.Task; import com.mongodb.stitch.android.core.StitchAppClient; import com.mongodb.stitch.android.core.auth.StitchAuth; import com.mongodb.stitch.android.core.auth.StitchAuthListener; import com.mongodb.stitch.android.core.auth.StitchUser; import com.mongodb.stitch.android.core.auth.internal.StitchAuthImpl; import com.mongodb.stitch.android.core.internal.common.MainLooperDispatcher; import com.mongodb.stitch.android.core.internal.common.TaskDispatcher; import com.mongodb.stitch.android.core.push.StitchPush; import com.mongodb.stitch.android.core.push.internal.StitchPushImpl; import com.mongodb.stitch.android.core.services.StitchServiceClient; import com.mongodb.stitch.android.core.services.internal.NamedServiceClientFactory; import com.mongodb.stitch.android.core.services.internal.ServiceClientFactory; import com.mongodb.stitch.android.core.services.internal.StitchServiceClientImpl; import com.mongodb.stitch.core.StitchAppClientConfiguration; import com.mongodb.stitch.core.StitchAppClientInfo; import com.mongodb.stitch.core.auth.internal.CoreStitchAuth; import com.mongodb.stitch.core.internal.CoreStitchAppClient; import com.mongodb.stitch.core.internal.common.AuthMonitor; import com.mongodb.stitch.core.internal.net.StitchAppRequestClientImpl; import com.mongodb.stitch.core.internal.net.StitchAppRoutes; import com.mongodb.stitch.core.services.internal.AuthEvent; import com.mongodb.stitch.core.services.internal.CoreStitchServiceClient; import com.mongodb.stitch.core.services.internal.CoreStitchServiceClientImpl; import com.mongodb.stitch.core.services.internal.RebindEvent; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import javax.annotation.Nullable; import org.bson.codecs.Decoder; import org.bson.codecs.configuration.CodecRegistry; public final class StitchAppClientImpl implements StitchAppClient, AuthMonitor, StitchAuthListener { private final CoreStitchAppClient coreClient; private final TaskDispatcher dispatcher; private final StitchAppClientInfo info; private final StitchAppRoutes routes; private final StitchAuthImpl auth; private final StitchPush push; /** * A list of weak references to any service client created * by a user. */ private final List<WeakReference<CoreStitchServiceClient>> serviceClients; /** * Constructs an app client with the given configuration. * * @param clientAppId the client app id for the app. * @param config the configuration to use for the app client. */ public StitchAppClientImpl( final String clientAppId, final StitchAppClientConfiguration config ) { this.dispatcher = new TaskDispatcher(); this.info = new StitchAppClientInfo( clientAppId, config.getDataDirectory(), config.getLocalAppName(), config.getLocalAppVersion(), config.getCodecRegistry(), config.getNetworkMonitor(), this, new MainLooperDispatcher()); this.routes = new StitchAppRoutes(this.info.getClientAppId()); final StitchAppRequestClientImpl requestClient = new StitchAppRequestClientImpl(clientAppId, config.getBaseUrl(), config.getTransport(), config.getDefaultRequestTimeout()); this.auth = new StitchAuthImpl( requestClient, this.routes.getAuthRoutes(), config.getStorage(), dispatcher, this.info); this.coreClient = new CoreStitchAppClient(this.auth, this.routes, config.getCodecRegistry()); this.push = new StitchPushImpl(this.auth, this.routes.getPushRoutes(), dispatcher); this.serviceClients = new ArrayList<>(); this.auth.addSynchronousAuthListener(this); } @Override public StitchAuth getAuth() { return auth; } @Override public StitchPush getPush() { return push; } @Override public <T> T getServiceClient( final NamedServiceClientFactory<T> factory, final String serviceName) { final CoreStitchServiceClient serviceClient = new CoreStitchServiceClientImpl( auth, routes.getServiceRoutes(), serviceName, info.getCodecRegistry()); this.bindServiceClient(serviceClient); return factory.getClient(serviceClient, info, dispatcher); } @Override public <T> T getServiceClient(final ServiceClientFactory<T> factory) { final CoreStitchServiceClient serviceClient = new CoreStitchServiceClientImpl( auth, routes.getServiceRoutes(), "", info.getCodecRegistry()); this.bindServiceClient(serviceClient); return factory.getClient(serviceClient, info, dispatcher); } @Override public StitchServiceClient getServiceClient(final String serviceName) { final CoreStitchServiceClient serviceClient = new CoreStitchServiceClientImpl( auth, routes.getServiceRoutes(), serviceName, info.getCodecRegistry()); this.bindServiceClient(serviceClient); return new StitchServiceClientImpl(serviceClient, dispatcher); } @Override public Task<Void> callFunction( final String name, final List<?> args) { return dispatcher.dispatchTask( new Callable<Void>() { @Override public Void call() { coreClient.callFunction(name, args, null); return null; } }); } @Override public Task<Void> callFunction( final String name, final List<?> args, final Long requestTimeout ) { return dispatcher.dispatchTask( new Callable<Void>() { @Override public Void call() { coreClient.callFunction(name, args, requestTimeout); return null; } }); } @Override public <ResultT> Task<ResultT> callFunction( final String name, final List<?> args, final Class<ResultT> resultClass) { return dispatcher.dispatchTask( new Callable<ResultT>() { @Override public ResultT call() { return coreClient.callFunction(name, args, null, resultClass); } }); } @Override public <ResultT> Task<ResultT> callFunction( final String name, final List<?> args, final Long requestTimeout, final Class<ResultT> resultClass) { return dispatcher.dispatchTask( new Callable<ResultT>() { @Override public ResultT call() { return coreClient.callFunction(name, args, requestTimeout, resultClass); } }); } @Override public <ResultT> Task<ResultT> callFunction( final String name, final List<?> args, final Class<ResultT> resultClass, final CodecRegistry codecRegistry ) { return dispatcher.dispatchTask( new Callable<ResultT>() { @Override public ResultT call() { return coreClient.callFunction(name, args, null, resultClass, codecRegistry); } }); } @Override public <ResultT> Task<ResultT> callFunction( final String name, final List<?> args, final Long requestTimeout, final Class<ResultT> resultClass, final CodecRegistry codecRegistry ) { return dispatcher.dispatchTask( new Callable<ResultT>() { @Override public ResultT call() { return coreClient.callFunction( name, args, requestTimeout, resultClass, codecRegistry); } }); } @Override public <ResultT> Task<ResultT> callFunction( final String name, final List<?> args, final Decoder<ResultT> resultDecoder) { return dispatcher.dispatchTask( new Callable<ResultT>() { @Override public ResultT call() { return coreClient.callFunction(name, args, null, resultDecoder); } }); } @Override public <ResultT> Task<ResultT> callFunction( final String name, final List<?> args, final Long requestTimeout, final Decoder<ResultT> resultDecoder) { return dispatcher.dispatchTask( new Callable<ResultT>() { @Override public ResultT call() { return coreClient.callFunction(name, args, requestTimeout, resultDecoder); } }); } @Override public boolean isLoggedIn() throws InterruptedException { return ((CoreStitchAuth)getAuth()).isLoggedInInterruptibly(); } @Override public boolean tryIsLoggedIn() { try { return ((CoreStitchAuth)getAuth()).isLoggedInInterruptibly(); } catch (InterruptedException e) { return false; } } @Nullable @Override public String getActiveUserId() { return getAuth().getUser() != null ? getAuth().getUser().getId() : null; } /** * Bind a given service client to this app client. * This allows the app client to dispatch events that require * the service to rebind to the application, e.g., a change * in authentication. * * @param coreStitchServiceClient the service client to bind */ private void bindServiceClient(final CoreStitchServiceClient coreStitchServiceClient) { this.serviceClients.add(new WeakReference<>(coreStitchServiceClient)); } private void onRebindEvent(final RebindEvent rebindEvent) { final Iterator<WeakReference<CoreStitchServiceClient>> iterator = this.serviceClients.iterator(); while (iterator.hasNext()) { final WeakReference<CoreStitchServiceClient> weakReference = iterator.next(); final CoreStitchServiceClient binder = weakReference.get(); // if the binder has been dealloc'd, remove it from the list // else, notify the binder that a rebind event has occurred if (binder == null) { this.serviceClients.remove(weakReference); } else { binder.onRebindEvent(rebindEvent); } } } @Override public void onAuthEvent(final StitchAuth auth) { } @Override public void onUserLoggedIn(final StitchAuth auth, final StitchUser loggedInUser) { onRebindEvent(new AuthEvent.UserLoggedIn<>(loggedInUser)); } @Override public void onUserLoggedOut(final StitchAuth auth, final StitchUser loggedOutUser) { onRebindEvent(new AuthEvent.UserLoggedOut<>(loggedOutUser)); } @Override public void onActiveUserChanged(final StitchAuth auth, final StitchUser currentActiveUser, final @Nullable StitchUser previousActiveUser) { onRebindEvent(new AuthEvent.ActiveUserChanged<>(currentActiveUser, previousActiveUser)); } @Override public void onUserRemoved(final StitchAuth auth, final StitchUser removedUser) { onRebindEvent(new AuthEvent.UserRemoved<>(removedUser)); } /** * Closes the client and shuts down all background operations. */ @Override public void close() throws IOException { auth.close(); dispatcher.close(); } }