001package com.gigya.android.sdk.account;
002
003import com.gigya.android.sdk.Config;
004import com.gigya.android.sdk.account.models.GigyaAccount;
005import com.gigya.android.sdk.utils.ObjectUtils;
006import com.google.gson.Gson;
007import com.google.gson.reflect.TypeToken;
008
009import java.util.HashMap;
010import java.util.Map;
011import java.util.concurrent.TimeUnit;
012
013/**
014 * Account handling service responsible for holding a cached copy of the current account instance.
015 *
016 * @param <A> Typed account instance (extends GigyaAccount).
017 */
018public class accountCacheService<A extends GigyaAccount> implements IAccountService<A> {
019
020    final private Config _config;
021
022    /*
023    Cached generic account object.
024     */
025    private String _cachedAccount;
026
027    @SuppressWarnings("unchecked")
028    private Class<A> _accountScheme;
029
030    /*
031    Invalidation timestamp for cached account.
032     */
033    private long _accountInvalidationTimestamp = 0L;
034    /*
035    Override account caching flag. Set to TRUE to override caching policy. This will result in consecutive getAccount HTTP requests firing
036    every time the user will request "getAccount" from the SDK.
037     */
038    private boolean _accountOverrideCache = false;
039
040
041    /*
042    Saving reference to the last "include" call parameter.
043     */
044    private String _lastRequestedInclude;
045
046    /*
047    Saving reference to the last "profileExtraFields" call parameter.
048     */
049    private String _lastRequestedProfileExtraFields;
050
051
052    public accountCacheService(Config config, GigyaAccountClass<A> accountClazz) {
053        _config = config;
054        _accountScheme = accountClazz.getAccountClass();
055    }
056
057    @Override
058    public void setAccountScheme(Class<A> scheme) {
059        _accountScheme = scheme;
060    }
061
062    @Override
063    public Class<A> getAccountSchema() {
064        return _accountScheme;
065    }
066
067    @Override
068    public void setAccount(String json) {
069        _cachedAccount = json;
070        nextAccountInvalidationTimestamp();
071    }
072
073    @Override
074    public void invalidateAccount() {
075        _cachedAccount = null;
076    }
077
078    @Override
079    public A getAccount() {
080        return new Gson().fromJson(_cachedAccount, _accountScheme);
081    }
082
083    @Override
084    public boolean isCachedAccount(String include, String profileExtraFields) {
085        if (!(!_accountOverrideCache && _cachedAccount != null && System.currentTimeMillis() < _accountInvalidationTimestamp)) {
086            return false;
087        }
088        if (!ObjectUtils.nullAllowedEquals(include, _lastRequestedInclude)) {
089            return false;
090        }
091        return ObjectUtils.nullAllowedEquals(profileExtraFields, _lastRequestedProfileExtraFields);
092    }
093
094    @Override
095    public void nextAccountInvalidationTimestamp() {
096        if (_cachedAccount == null) {
097            return;
098        }
099        final int accountCacheTime = _config.getAccountCacheTime();
100        if (!_accountOverrideCache && accountCacheTime > 0) {
101            _accountInvalidationTimestamp = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(accountCacheTime);
102        }
103    }
104
105    @Override
106    public long getNextInvalidationTimestamp() {
107        return _accountInvalidationTimestamp;
108    }
109
110    @Override
111    public void setAccountOverrideCache(boolean accountOverrideCache) {
112        _accountOverrideCache = accountOverrideCache;
113    }
114
115    @Override
116    public void updateExtendedParametersRequest(String include, String profileExtraFields) {
117        // Null fields are viable.
118        _lastRequestedInclude = include;
119        _lastRequestedProfileExtraFields = profileExtraFields;
120    }
121
122    //region ACCOUNT SPECIFIC LOGIC
123
124    /**
125     * Get account object objectDifference.
126     */
127    @SuppressWarnings({"ConstantConditions"})
128    @Override
129    public Map<String, Object> calculateDiff(Gson gson, A cachedAccount, A updatedAccount) {
130        /* Map updated account object to JSON -> Map. */
131        final String updatedJson = gson.toJson(updatedAccount);
132        Map<String, Object> updatedMap = gson.fromJson(updatedJson, new TypeToken<HashMap<String, Object>>() {
133        }.getType());
134
135        /* Map original account object to JSON -> Map. */
136        final String originalJson = gson.toJson(cachedAccount);
137        Map<String, Object> originalMap = gson.fromJson(originalJson, new TypeToken<HashMap<String, Object>>() {
138        }.getType());
139
140        /* Calculate objectDifference. */
141        Map<String, Object> diff = ObjectUtils.objectDifference(originalMap, updatedMap);
142        /* Must have UID or regToken. */
143        if (updatedMap.containsKey("UID")) {
144            diff.put("UID", updatedMap.get("UID"));
145        } else if (updatedMap.containsKey("regToken")) {
146            diff.put("regToken", updatedMap.get("regToken"));
147        }
148        serializeObjectFields(gson, diff);
149        return diff;
150    }
151
152    /**
153     * Object represented field values must be set as JSON Objects. So we are reverting each one to
154     * its JSON String representation.
155     */
156    public static void serializeObjectFields(Gson gson, Map<String, Object> map) {
157        for (Map.Entry<String, Object> entry : map.entrySet()) {
158            if (entry.getValue() instanceof Map) {
159                map.put(entry.getKey(), gson.toJson(entry.getValue()));
160            }
161        }
162    }
163
164    //endregion
165}