Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: SQDSDKS-5855 - Identity caching #452

Open
wants to merge 5 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 72 additions & 7 deletions android-core/src/main/java/com/mparticle/identity/IdentityApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
Expand All @@ -46,13 +48,18 @@ public class IdentityApi {
MessageManager mMessageManager;
KitManager mKitManager;
private Internal mInternal = new Internal();
private long timeoutSeconds = 0L;

MParticleUserDelegate mUserDelegate;
private MParticleIdentityClient mApiClient;

Set<IdentityStateListener> identityStateListeners = new HashSet<IdentityStateListener>();
private static Object lock = new Object();

public static final String LOGIN_CALL = "login";
public static final String IDENTIFY_CALL = "identify";
public static final String LOGOUT_CALL = "logout";

protected IdentityApi() {
}

Expand Down Expand Up @@ -165,6 +172,7 @@ public MParticleTask<IdentityApiResult> logout() {
*/
@NonNull
public MParticleTask<IdentityApiResult> logout(@Nullable final IdentityApiRequest logoutRequest) {
resetCache();
return makeIdentityRequest(logoutRequest, new IdentityNetworkRequestRunnable() {
@Override
public IdentityHttpResponse request(IdentityApiRequest request) throws Exception {
Expand All @@ -175,7 +183,7 @@ public IdentityHttpResponse request(IdentityApiRequest request) throws Exception
public void onPostExecute(IdentityApiResult result) {
mKitManager.onLogoutCompleted(result.getUser(), logoutRequest);
}
});
}, false, LOGOUT_CALL);
}

/**
Expand Down Expand Up @@ -203,14 +211,16 @@ public MParticleTask<IdentityApiResult> login(@Nullable final IdentityApiRequest
return makeIdentityRequest(loginRequest, new IdentityNetworkRequestRunnable() {
@Override
public IdentityHttpResponse request(IdentityApiRequest request) throws Exception {
return getApiClient().login(request);
IdentityHttpResponse response = getApiClient().login(request);
timeoutSeconds = response.getTimeout();
return response;
}

@Override
public void onPostExecute(IdentityApiResult result) {
mKitManager.onLoginCompleted(result.getUser(), loginRequest);
}
});
}, true, LOGIN_CALL);
}

/**
Expand All @@ -226,14 +236,16 @@ public MParticleTask<IdentityApiResult> identify(@Nullable final IdentityApiRequ
return makeIdentityRequest(identifyRequest, new IdentityNetworkRequestRunnable() {
@Override
public IdentityHttpResponse request(IdentityApiRequest request) throws Exception {
return getApiClient().identify(request);
IdentityHttpResponse response = getApiClient().identify(request);
timeoutSeconds = response.getTimeout();
return response;
}

@Override
public void onPostExecute(IdentityApiResult result) {
mKitManager.onIdentifyCompleted(result.getUser(), identifyRequest);
}
});
}, true, IDENTIFY_CALL);
}

/**
Expand All @@ -252,6 +264,7 @@ public BaseIdentityTask modify(@NonNull final IdentityApiRequest updateRequest)
if (updateRequest.mpid == null) {
updateRequest.mpid = mConfigManager.getMpid();
}

if (Constants.TEMPORARY_MPID.equals(updateRequest.mpid)) {
String message = "modify() requires a non-zero MPID, please make sure a MParticleUser is present before making a modify request.";
if (devMode) {
Expand All @@ -266,10 +279,12 @@ public BaseIdentityTask modify(@NonNull final IdentityApiRequest updateRequest)
@Override
public void run() {
try {
resetCache();
final IdentityHttpResponse result = getApiClient().modify(updateRequest);
if (!result.isSuccessful()) {
task.setFailed(result);
} else {
timeoutSeconds = result.getTimeout();
MParticleUserDelegate.setUserIdentities(mUserDelegate, updateRequest.getUserIdentities(), updateRequest.mpid);
task.setSuccessful(new IdentityApiResult(MParticleUserImpl.getInstance(mContext, updateRequest.mpid, mUserDelegate), null));
new Handler(Looper.getMainLooper()).post(new Runnable() {
Expand Down Expand Up @@ -348,20 +363,67 @@ private void reset() {
}
}

private BaseIdentityTask makeIdentityRequest(IdentityApiRequest request, final IdentityNetworkRequestRunnable networkRequest) {
private boolean shouldMakeRequest(IdentityApiRequest identityRequest, boolean acceptCachedResponse, long lastIdentityCall) {
if (!acceptCachedResponse || !mConfigManager.isIdentityCachingEnabled()) {
return true;
}
boolean hasTimedOut = lastIdentityCall == -1L || (lastIdentityCall + (timeoutSeconds * 1000) > System.currentTimeMillis());
if (identityRequest != null && identityRequest.mpid != null) {
MParticleUser user = getUser(identityRequest.mpid);
if (hasTimedOut || isRequestDifferent(user, identityRequest)) {
return true;
} else {
return false;
}
} else {
return true;
}
}

private boolean isRequestDifferent(MParticleUser user, IdentityApiRequest identityRequest) {
return areIdentitiesDifferent(user, identityRequest) || mpIdNotKnown(user);
}

private boolean mpIdNotKnown(MParticleUser user) {
return user != null && !mConfigManager.getMpids().contains(user.getId());
}

private boolean areIdentitiesDifferent(MParticleUser user, IdentityApiRequest identityApiRequest) {
if (user != null) {
Map<MParticle.IdentityType, String> userIdentities = user.getUserIdentities() != null ? user.getUserIdentities() : new HashMap<>();
Map<MParticle.IdentityType, String> requestUserIdentities = identityApiRequest.getUserIdentities() != null ? identityApiRequest.getUserIdentities() : new HashMap<>();
return !userIdentities.equals(requestUserIdentities);
} else {
return true;
}
}

private void resetCache() {
mConfigManager.resetIdentityTypeCall();
}

private BaseIdentityTask makeIdentityRequest(IdentityApiRequest request, final IdentityNetworkRequestRunnable networkRequest, boolean acceptCachedResponse, String call) {
long lastIdentityCallTime = mConfigManager.getLastIdentityTypeCall(call);
if (request == null) {
request = IdentityApiRequest.withEmptyUser().build();
}
final BaseIdentityTask task = new BaseIdentityTask();
ConfigManager.setIdentityRequestInProgress(true);
final IdentityApiRequest identityApiRequest = request;
if (!shouldMakeRequest(identityApiRequest, acceptCachedResponse, lastIdentityCallTime)) {
//Set both current and prev user as the current one, no request was done.
task.setSuccessful(new IdentityApiResult(getUser(identityApiRequest.mpid), getCurrentUser()));
Logger.debug("Identity - Returning current user from cache");
return task;
}
ConfigManager.setIdentityRequestInProgress(true);
mBackgroundHandler.post(new Runnable() {
@Override
public void run() {
synchronized (lock) {
if (mBackgroundHandler.isDisabled()) {
return;
}
Logger.debug("Identity - Making identity call for request");
try {
long startingMpid = mConfigManager.getMpid();
final IdentityHttpResponse result = networkRequest.request(identityApiRequest);
Expand All @@ -375,6 +437,9 @@ public void run() {
ConfigManager.setIdentityRequestInProgress(false);
mUserDelegate.setUser(mContext, startingMpid, newMpid, identityApiRequest.getUserIdentities(), identityApiRequest.getUserAliasHandler(), isLoggedIn);
final MParticleUser previousUser = startingMpid != newMpid ? getUser(startingMpid) : null;
if (acceptCachedResponse) {
mConfigManager.setLastIdentityTypeCall(call);
}
task.setSuccessful(new IdentityApiResult(MParticleUserImpl.getInstance(mContext, newMpid, mUserDelegate), previousUser));
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public final class IdentityHttpResponse {
private String context;
private int httpCode;
private boolean loggedIn;
private long timeout = 0L;

@NonNull
public static final String MPID = "mpid";
Expand Down Expand Up @@ -50,8 +51,9 @@ public IdentityHttpResponse(int code, @NonNull String errorString) {
this.errors.add(new Error(UNKNOWN, errorString));
}

public IdentityHttpResponse(int httpCode, @Nullable JSONObject jsonObject) throws JSONException {
public IdentityHttpResponse(int httpCode, @Nullable JSONObject jsonObject, long identityTimeout) throws JSONException {
this.httpCode = httpCode;
this.timeout = identityTimeout;
if (!MPUtility.isEmpty(jsonObject)) {
if (jsonObject.has(MPID)) {
this.mpId = Long.valueOf(jsonObject.getString(MPID));
Expand Down Expand Up @@ -92,6 +94,10 @@ public long getMpId() {
return mpId;
}

public long getTimeout() {
return timeout;
}

@Nullable
public String getContext() {
return context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public class MParticleIdentityClientImpl extends MParticleBaseClientImpl impleme
static final String DEVICE_APPLICATION_STAMP = "device_application_stamp";
static final String KNOWN_IDENTITIES = "known_identities";
static final String PREVIOUS_MPID = "previous_mpid";
static final String IDENTITY_HEADER_TIMEOUT = "X-MP-Max-Age";

static final String NEW_VALUE = "new_value";
static final String OLD_VALUE = "old_value";
Expand Down Expand Up @@ -80,10 +81,11 @@ public IdentityHttpResponse login(IdentityApiRequest request) throws JSONExcepti
String url = connection.getURL().toString();
InternalListenerManager.getListener().onNetworkRequestStarted(SdkListener.Endpoint.IDENTITY_LOGIN, url, jsonObject, request);
connection = makeUrlRequest(Endpoint.IDENTITY, connection, jsonObject.toString(), false);
String headerField = connection.getHeaderField(IDENTITY_HEADER_TIMEOUT);
int responseCode = connection.getResponseCode();
JSONObject response = MPUtility.getJsonResponse(connection);
InternalListenerManager.getListener().onNetworkRequestFinished(SdkListener.Endpoint.IDENTITY_LOGIN, url, response, responseCode);
return parseIdentityResponse(responseCode, response);
return parseIdentityResponse(responseCode, response, headerField);
}

public IdentityHttpResponse logout(IdentityApiRequest request) throws JSONException, IOException {
Expand All @@ -96,7 +98,7 @@ public IdentityHttpResponse logout(IdentityApiRequest request) throws JSONExcept
int responseCode = connection.getResponseCode();
JSONObject response = MPUtility.getJsonResponse(connection);
InternalListenerManager.getListener().onNetworkRequestFinished(SdkListener.Endpoint.IDENTITY_LOGOUT, url, response, responseCode);
return parseIdentityResponse(responseCode, response);
return parseIdentityResponse(responseCode, response, "0");
}

public IdentityHttpResponse identify(IdentityApiRequest request) throws JSONException, IOException {
Expand All @@ -106,10 +108,11 @@ public IdentityHttpResponse identify(IdentityApiRequest request) throws JSONExce
String url = connection.getURL().toString();
InternalListenerManager.getListener().onNetworkRequestStarted(SdkListener.Endpoint.IDENTITY_IDENTIFY, url, jsonObject, request);
connection = makeUrlRequest(Endpoint.IDENTITY, connection, jsonObject.toString(), false);
String headerField = connection.getHeaderField(IDENTITY_HEADER_TIMEOUT);
int responseCode = connection.getResponseCode();
JSONObject response = MPUtility.getJsonResponse(connection);
InternalListenerManager.getListener().onNetworkRequestFinished(SdkListener.Endpoint.IDENTITY_IDENTIFY, url, response, responseCode);
return parseIdentityResponse(responseCode, response);
return parseIdentityResponse(responseCode, response, headerField);
}

public IdentityHttpResponse modify(IdentityApiRequest request) throws JSONException, IOException {
Expand All @@ -124,9 +127,10 @@ public IdentityHttpResponse modify(IdentityApiRequest request) throws JSONExcept
InternalListenerManager.getListener().onNetworkRequestStarted(SdkListener.Endpoint.IDENTITY_MODIFY, url, jsonObject, request);
connection = makeUrlRequest(Endpoint.IDENTITY, connection, jsonObject.toString(), false);
int responseCode = connection.getResponseCode();
String headerField = connection.getHeaderField(IDENTITY_HEADER_TIMEOUT);
JSONObject response = MPUtility.getJsonResponse(connection);
InternalListenerManager.getListener().onNetworkRequestFinished(SdkListener.Endpoint.IDENTITY_MODIFY, url, response, responseCode);
return parseIdentityResponse(responseCode, response);
return parseIdentityResponse(responseCode, response, headerField);
}

private JSONObject getBaseJson() throws JSONException {
Expand Down Expand Up @@ -238,13 +242,18 @@ private JSONObject getChangeJson(IdentityApiRequest request) throws JSONExceptio
return jsonObject;
}

private IdentityHttpResponse parseIdentityResponse(int httpCode, JSONObject jsonObject) {
private IdentityHttpResponse parseIdentityResponse(int httpCode, JSONObject jsonObject, String identityTimeoutHeader) {
long timeoutHeader = 0L;
try {
Logger.verbose("Identity response code: " + httpCode);
if (jsonObject != null) {
Logger.verbose("Identity result: " + jsonObject.toString());
}
IdentityHttpResponse httpResponse = new IdentityHttpResponse(httpCode, jsonObject);
try {
timeoutHeader = Long.parseLong(identityTimeoutHeader);
} catch (Exception e) {
}
IdentityHttpResponse httpResponse = new IdentityHttpResponse(httpCode, jsonObject, timeoutHeader);
if (!MPUtility.isEmpty(httpResponse.getContext())) {
mConfigManager.setIdentityApiContext(httpResponse.getContext());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public class ConfigManager {
static final String DATAPLAN_BLOCK_USER_IDENTITIES = "id";
public static final String KIT_CONFIG_KEY = "kit_config";
static final String MIGRATED_TO_KIT_SHARED_PREFS = "is_mig_kit_sp";
private static final String IDENTITY_CACHING_ENABLED = "identityCachingEnabled";

private static final int DEVMODE_UPLOAD_INTERVAL_MILLISECONDS = 10 * 1000;
private static final int DEFAULT_MAX_ALIAS_WINDOW_DAYS = 90;
Expand All @@ -92,6 +93,7 @@ public class ConfigManager {
private JSONObject mProviderPersistence;
private int mRampValue = -1;
private int mUserBucket = -1;
private boolean identityCachingEnabled = false;

private int mSessionTimeoutInterval = -1;
private int mUploadInterval = -1;
Expand Down Expand Up @@ -421,6 +423,9 @@ private synchronized void updateCoreConfig(JSONObject responseJSON, boolean newC
mSendOoEvents = false;
}

//TODO Read from identityCachingEnabled feature flag
editor.putBoolean(IDENTITY_CACHING_ENABLED, identityCachingEnabled);

if (responseJSON.has(ProviderPersistence.KEY_PERSISTENCE)) {
setProviderPersistence(new ProviderPersistence(responseJSON, mContext));
} else {
Expand Down Expand Up @@ -527,6 +532,10 @@ public long getInfluenceOpenTimeoutMillis() {
return mInfluenceOpenTimeout;
}

public boolean isIdentityCachingEnabled() {
return identityCachingEnabled;
}

private void applyConfig() {
if (getLogUnhandledExceptions()) {
enableUncaughtExceptionLogging(false);
Expand Down Expand Up @@ -1251,6 +1260,21 @@ public int getIdentityConnectionTimeout() {
return sPreferences.getInt(Constants.PrefKeys.IDENTITY_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT_SECONDS) * 1000;
}

public long getLastIdentityTypeCall(String call) {
return sPreferences.getLong(call, 0L);
}

public void resetIdentityTypeCall() {
SharedPreferences.Editor editor = sPreferences.edit();
editor.putLong(IdentityApi.LOGIN_CALL, -1);
editor.putLong(IdentityApi.IDENTIFY_CALL, -1);
editor.apply();
}

public void setLastIdentityTypeCall(String call) {
sPreferences.edit().putLong(call, System.currentTimeMillis()).apply();
}

public int getConnectionTimeout() {
return DEFAULT_CONNECTION_TIMEOUT_SECONDS * 1000;
}
Expand Down
Loading