/* * Copyright (c) 2015 Ushahidi Inc * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program in the file LICENSE-AGPL. If not, see * https://www.gnu.org/licenses/agpl-3.0.html */ package com.ushahidi.platform.mobile.app.data.api.account; import android.content.SharedPreferences; import android.support.annotation.NonNull; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import javax.inject.Inject; /** * Implementation of {@link SessionManager} that persists user accounts using {@link * SharedPreferences} * * @param <T> The platform session * @author Ushahidi Team <team@ushahidi.com> */ public class PersistedSessionManager<T extends Session> implements SessionManager<T> { private static final int NUM_SESSIONS = 1; private static final String ACTIVE_SESSION_KEY = "active_session"; private final SharedPreferences mSharedPreferences; private final String mPrefKeyActiveSession; private final String mPrefKeySession; private final SerializationStrategy<T> mSerializer; private final ConcurrentHashMap<String, T> mSessionMap; private final AtomicReference<T> mActiveSessionRef; private volatile boolean restorePending = true; /** * Default constructor * * @param sharedPreferences The shared preferences * @param serializer The serializer to be used for serialization * @param prefKeyActiveSession The active session preference key * @param prefKeySession the preference key for the user's session */ @Inject public PersistedSessionManager(SharedPreferences sharedPreferences, SerializationStrategy<T> serializer, String prefKeyActiveSession, String prefKeySession) { this(sharedPreferences, serializer, new ConcurrentHashMap<>(NUM_SESSIONS), prefKeyActiveSession, prefKeySession); } private PersistedSessionManager(SharedPreferences sharedPreferences, SerializationStrategy<T> serializer, ConcurrentHashMap<String, T> sessionMap, String prefKeyActiveSession, String prefKeySession) { mSharedPreferences = sharedPreferences; mSerializer = serializer; mSessionMap = sessionMap; mActiveSessionRef = new AtomicReference<>(); mPrefKeyActiveSession = prefKeyActiveSession; mPrefKeySession = prefKeySession; } /** * Restore all session if necessary */ void restoreAllSessionsIfNecessary() { // Only restore once if (restorePending) { restoreAllSessions(); } } private synchronized void restoreAllSessions() { if (restorePending) { restoreActiveSession(); restoreSessions(); restorePending = false; } } private void restoreSessions() { T session; final Map<String, ?> preferences = mSharedPreferences.getAll(); for (Map.Entry<String, ?> entry : preferences.entrySet()) { if (isSessionPreferenceKey(entry.getKey())) { session = mSerializer.deserialize((String) entry.getValue()); if (session != null) { internalSetSession(session.getId(), session.getDeploymentId(), session, false); } } } } private void restoreActiveSession() { final T session = mSerializer .deserialize(mSharedPreferences.getString(ACTIVE_SESSION_KEY, "")); if (session != null) { internalSetSession(session.getId(), session.getDeploymentId(), session, false); } } /** * Determine if session's preference key exist * * @param preferenceKey The preference key * @return The preference key */ boolean isSessionPreferenceKey(String preferenceKey) { return preferenceKey.startsWith(mPrefKeySession); } @Override public T getActiveSession() { restoreAllSessionsIfNecessary(); return mActiveSessionRef.get(); } @Override public void setActiveSession(@NonNull T session) { restoreAllSessionsIfNecessary(); internalSetSession(session.getId(), session.getDeploymentId(), session, true); } @Override public void clearActiveSession() { restoreAllSessionsIfNecessary(); if (mActiveSessionRef.get() != null) { clearSession(mActiveSessionRef.get().getId(), mActiveSessionRef.get().getDeploymentId()); } } @Override public T getSession(long id, long deploymentId) { restoreAllSessionsIfNecessary(); return mSessionMap.get(getPrefKey(id, deploymentId)); } @Override public void setSession(T session) { if (session == null) { throw new IllegalArgumentException("Session must not be null!"); } restoreAllSessionsIfNecessary(); internalSetSession(session.getId(), session.getDeploymentId(), session, false); } @Override public void clearSession(long id, long deploymentId) { restoreAllSessionsIfNecessary(); if ((mActiveSessionRef.get() != null) && (mActiveSessionRef.get().getId() == id) && ( mActiveSessionRef.get().getDeploymentId() == deploymentId)) { synchronized (this) { mActiveSessionRef.set(null); mSharedPreferences.edit().remove(getPrefKey(id, deploymentId)).commit(); } } mSessionMap.remove(getSessionMapKey(id, deploymentId)); } @Override public Map<String, T> getSessionMap() { restoreAllSessionsIfNecessary(); return Collections.unmodifiableMap(mSessionMap); } private void internalSetSession(long id, long deploymentId, T session, boolean forceUpdate) { mSessionMap.put(getSessionMapKey(id, deploymentId), session); mSharedPreferences.edit() .putString(getPrefKey(id, deploymentId), mSerializer.serialize(session)) .commit(); final T activeSession = mActiveSessionRef.get(); if (activeSession == null || forceUpdate) { synchronized (this) { mActiveSessionRef.compareAndSet(activeSession, session); mSharedPreferences.edit() .putString(ACTIVE_SESSION_KEY, mSerializer.serialize(session)); } } } /** * Gets the preference key. It's a combination of separator, user id a separator and the * deployment associated with the user * * @param id The session id which is the same as the user's id * @param deploymentId The deployment the session is tired to. * @return The preference key */ String getPrefKey(long id, long deploymentId) { return mPrefKeySession + "_" + id + "_" + deploymentId; } /** * Gets the session map's key. It's a combination of separator, user id a separator and the * deployment associated with the user * * @param id The session id which is the same as the user's id * @param deploymentId The deployment the session is tired to. * @return The session map key */ String getSessionMapKey(long id, long deploymentId) { return mPrefKeySession + "_" + id + "_" + deploymentId; } }