Here is the problem that needed to be solved:
1. Use session cache to cache some user information like their profile or user settings
2. When a external systems update the information and try to remove the session cache as it got updated it fails because it does not have access to a users session cache
Solution:
Build a session tracker in Org Cache to track missed removal of session cache
Step 1: When a session cache remove fails add it to the Org level tracker cache. The format of session keys are the following:
key + userId (Salesforce userId)
That way I can keep track of what userId’s I need to remove session cache
String splitStrKey = key.substring(0, key.length()-18);
String splitUserId = key.substring(key.length()-18, key.length());
will split into key and userId
public Boolean remove(String key) {
if (!System.isBatch() && !System.isFuture() && !Test.isRunningTest()){
Boolean removed = false;
if (sessionCacheEnabled && sessionPartition!=null) {
System.debug('inside session cache remove key: ' + key);
removed = sessionPartition.remove(key);
if (!removed && key!=null && key.length() > 18){
String splitStrKey = key.substring(0, key.length()-18);
String splitUserId = key.substring(key.length()-18, key.length());
System.debug('Session remove failed key ' + splitStrKey + ' for ' + splitUserId + ' adding to tracker');
removeFailedAddToTracker(splitStrKey, splitUserId, null);
}
} else if (orgPartition!=null){
removed = orgPartition.remove(key);
}
if (removed) {
System.debug(LoggingLevel.DEBUG, 'Removed key ' + key);
return true;
} else{
System.debug(LoggingLevel.DEBUG, 'Not removed key not found ' + key);
return false;
}
} else {
System.debug(LoggingLevel.DEBUG, 'Skipping cache remove in batch ' + key);
return false;
}
}
Step 2: Add session keys to Org level tracker cache (CACHE_USER_CACHE_TRACKER), we will add it as Map<Id, Map> where Map contains UserId as key and Map of all the session keys that needs to be removed
public void removeFailedAddToTracker(String keyPrefix, Id userId, Set<Id> clientIds){
if (App_Service.isIntegrationUser(userId)){
Map<String, String> mapOfContactUserIds = (Map<String, String>)getOrgPartition().get(App_Rest_Constants.CACHE_USER_CONTACT_MAP);
Map<Id, Map<String, Id>> mapOfExistingCacheQueueTracker = (Map<Id, Map<String, Id>>)getOrgPartition().get(App_Rest_Constants.CACHE_USER_CACHE_TRACKER);
if (clientIds!=null && !clientIds.isEmpty()){
for (Id clientId : clientIds){
if (mapOfContactUserIds!=null && mapOfContactUserIds.containsKey(clientId)){
Id userIdToClearCache = mapOfContactUserIds.get(clientId);
if (mapOfExistingCacheQueueTracker!=null){
if (mapOfExistingCacheQueueTracker.containsKey(userIdToClearCache)){
Map<String, Id> existingCacheStrings = mapOfExistingCacheQueueTracker.get(userIdToClearCache);
existingCacheStrings.put(keyPrefix, userIdToClearCache);
mapOfExistingCacheQueueTracker.put(userIdToClearCache, existingCacheStrings);
} else {
mapOfExistingCacheQueueTracker.put(userIdToClearCache, new Map<String, Id>{keyPrefix=>userIdToClearCache});
}
} else {
mapOfExistingCacheQueueTracker = new Map<Id, Map<String, Id>>();
mapOfExistingCacheQueueTracker.put(userIdToClearCache, new Map<String, Id>{keyPrefix=>userIdToClearCache});
}
}
}
} else {
if (mapOfExistingCacheQueueTracker!=null && mapOfExistingCacheQueueTracker.containsKey(userId)){
Map<String, Id> existingMap = mapOfExistingCacheQueueTracker.get(userId);
existingMap.put(keyPrefix, userId);
mapOfExistingCacheQueueTracker.put(userId, existingMap);
} else {
if (mapOfExistingCacheQueueTracker==null){
mapOfExistingCacheQueueTracker = new Map<Id, Map<String, Id>>();
}
mapOfExistingCacheQueueTracker.put(userId, new Map<String, Id>{keyPrefix=>userId});
}
}
if (mapOfExistingCacheQueueTracker!=null)
getOrgPartition().put(App_Constants.CACHE_USER_CACHE_TRACKER, mapOfExistingCacheQueueTracker);
}
}
Step 3: Every time before we check if session cache contains a key we will see if the same key is contained in the CACHE_USER_CACHE_TRACKER cache. If yes remove it from the users session cache so that cache can be removed and new query can be done
public Boolean containsKey(String cacheKey){
Boolean containsKey = false;
if (!System.isBatch() && !System.isFuture() && !Test.isRunningTest()){
if (sessionCacheEnabled==false && orgPartition!=null){
if (orgPartition.get(cacheKey)!=null){
containsKey = true;
}
} else if (sessionCacheEnabled && sessionPartition!=null) {
if (sessionPartition.get(cacheKey)!=null){
containsKey = true;
Map<Id, Map<String, Id>> mapOfExistingCacheQueueTracker = (Map<Id, Map<String, Id>>)getOrgPartition().get(App_Rest_Constants.CACHE_USER_CACHE_TRACKER);
if (mapOfExistingCacheQueueTracker!=null && mapOfExistingCacheQueueTracker.containsKey(UserInfo.getUserId())){
Map<String, Id> flagsToClear = mapOfExistingCacheQueueTracker.get(UserInfo.getUserId());
Boolean removeFlag = false;
for (String flagToClear : flagsToClear.keySet()){
if (flagToClear.equalsIgnoreCase(cacheKey)){
String keyToRemove = flagToClear + flagsToClear.get(flagToClear);
Boolean removeItemFromCache = getSessionPartition().remove(keyToRemove);
removeFlag = true;
containsKey = false;
}
}
if (removeFlag){
for (String flagToClear : flagsToClear.keySet()){
flagsToClear.remove(cacheKey);
if (flagsToClear.isEmpty() && flagsToClear.size()==0){
mapOfExistingCacheQueueTracker.remove(UserInfo.getUserId());
} else {
mapOfExistingCacheQueueTracker.put(UserInfo.getUserId(), flagsToClear);
}
}
getOrgPartition().put(App_Rest_Constants.CACHE_USER_CACHE_TRACKER, mapOfExistingCacheQueueTracker);
}
}
}
}
}
return containsKey;
}