/* * Copyright 2016 Realm 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 io.realm.internal; import android.annotation.SuppressLint; import android.content.Context; import android.content.IntentFilter; import android.net.ConnectivityManager; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import io.realm.RealmConfiguration; import io.realm.SyncConfiguration; import io.realm.SyncManager; import io.realm.SyncSession; import io.realm.exceptions.DownloadingRealmInterruptedException; import io.realm.exceptions.RealmException; import io.realm.internal.network.NetworkStateReceiver; @SuppressWarnings({"unused", "WeakerAccess"}) // Used through reflection. See ObjectServerFacade @Keep public class SyncObjectServerFacade extends ObjectServerFacade { private static final String WRONG_TYPE_OF_CONFIGURATION = "'configuration' has to be an instance of 'SyncConfiguration'."; @SuppressLint("StaticFieldLeak") // private static Context applicationContext; private static volatile Method removeSessionMethod; @Override public void init(Context context) { // Trying to keep things out the public API is no fun :/ // Just use reflection on init. It is a one-time method call so should be acceptable. //noinspection TryWithIdenticalCatches try { // FIXME: Reflection can be avoided by moving some functions of SyncManager and ObjectServer out of public Class<?> syncManager = Class.forName("io.realm.ObjectServer"); Method method = syncManager.getDeclaredMethod("init", Context.class); method.setAccessible(true); method.invoke(null, context); } catch (NoSuchMethodException e) { throw new RealmException("Could not initialize the Realm Object Server", e); } catch (InvocationTargetException e) { throw new RealmException("Could not initialize the Realm Object Server", e); } catch (IllegalAccessException e) { throw new RealmException("Could not initialize the Realm Object Server", e); } catch (ClassNotFoundException e) { throw new RealmException("Could not initialize the Realm Object Server", e); } if (applicationContext == null) { applicationContext = context; applicationContext.registerReceiver(new NetworkStateReceiver(), new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); } } @Override public void realmClosed(RealmConfiguration configuration) { // Last Thread using the specified configuration is closed // delete the wrapped Java session if (configuration instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) configuration; invokeRemoveSession(syncConfig); } else { throw new IllegalArgumentException(WRONG_TYPE_OF_CONFIGURATION); } } @Override public String[] getUserAndServerUrl(RealmConfiguration config) { if (config instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) config; String rosServerUrl = syncConfig.getServerUrl().toString(); String rosUserIdentity = syncConfig.getUser().getIdentity(); String syncRealmAuthUrl = syncConfig.getUser().getAuthenticationUrl().toString(); String rosRefreshToken = syncConfig.getUser().getAccessToken().value(); return new String[]{rosUserIdentity, rosServerUrl, syncRealmAuthUrl, rosRefreshToken}; } else { return new String[4]; } } public static Context getApplicationContext() { return applicationContext; } @Override public void wrapObjectStoreSessionIfRequired(RealmConfiguration config) { if (config instanceof SyncConfiguration) { SyncManager.getSession((SyncConfiguration) config); } } //FIXME remove this reflection call once we redesign the SyncManager to separate interface // from implementation to avoid issue like exposing internal method like SyncManager#removeSession // or SyncSession#close. This happens because SyncObjectServerFacade is internal, whereas // SyncManager#removeSession or SyncSession#close are package private & should not be public. private void invokeRemoveSession(SyncConfiguration syncConfig) { try { if (removeSessionMethod == null) { synchronized (SyncObjectServerFacade.class) { if (removeSessionMethod == null) { Method removeSession = SyncManager.class.getDeclaredMethod("removeSession", SyncConfiguration.class); removeSession.setAccessible(true); removeSessionMethod = removeSession; } } } removeSessionMethod.invoke(null, syncConfig); } catch (NoSuchMethodException e) { throw new RealmException("Could not lookup method to remove session: " + syncConfig.toString(), e); } catch (InvocationTargetException e) { throw new RealmException("Could not invoke method to remove session: " + syncConfig.toString(), e); } catch (IllegalAccessException e) { throw new RealmException("Could not remove session: " + syncConfig.toString(), e); } } @Override public void downloadRemoteChanges(RealmConfiguration config) { if (config instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) config; if (syncConfig.shouldWaitForInitialRemoteData()) { SyncSession session = SyncManager.getSession(syncConfig); try { session.downloadAllServerChanges(); } catch (InterruptedException e) { throw new DownloadingRealmInterruptedException(syncConfig, e); } } } } @Override public boolean wasDownloadInterrupted(Throwable throwable) { return (throwable instanceof DownloadingRealmInterruptedException); } }