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; }