Apex Cache Tracker to remove session cache

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

Apex Check User Session Still Valid

Checking if a user has a valid session before making a query or call else an INVALID_SESSION error will be returned. The value of time remaining for the session can be cached and referenced so this query only runs a few times. Every time a user makes a request we can check the cache and see if the user has time remaining.

Query session information and returning UserSession model

public static UserSession getUserSessionInfo(User userObj){
      UserSession bpUserSession = new UserSession();
      List<AuthSession> session = [Select LastModifiedDate, NumSecondsValid from AuthSession where UsersId = : userObj.Id] ;
      bpUserSession.setSessionValid(false);

      for(AuthSession sessionObj : session){
          bpUserSession.setSecondsValid(sessionObj.NumSecondsValid);
          bpUserSession.setLastModifiedDate(sessionObj.LastModifiedDate);
      }

      if (bpUserSession.getSecondsValid()!=null)
        bpUserSession.setSessionExpireTime(bpUserSession.getLastModifiedDate().addSeconds(bpUserSession.getSecondsValid()));

      if( bpUserSession.getSessionExpireTime() > System.now())
          bpUserSession.setSessionValid(true);

      List<AggregateResult> loginHistoryObj = [SELECT MAX(LoginTime) FROM LoginHistory WHERE UserId = : userObj.Id GROUP BY UserId];

      DateTime loginDateTime = (DateTime)loginHistoryObj[0].get('expr0');
      bpUserSession.setLoginTime(loginDateTime);
      Date loginDate = loginDatetime.date();
      if( logindate != (DateTime.now()).date())
          bpUserSession.setSessionValid(false);

     return bpUserSession;
    }

User Session model to set session information

    public class UserSession {
      DateTime lastModifiedDate;
      Integer secondsValid;
      DateTime sessionExpireTime;
      Boolean sessionValid;
      DateTime loginTime;

      public void setLastModifiedDate(DateTime lastModifiedDate){
        this.lastModifiedDate = lastModifiedDate;
      }

      public DateTime getLastModifiedDate(){
        return lastModifiedDate;
      }

      public void setSecondsValid(Integer secondsValid){
        this.secondsValid = secondsValid;
      }

      public Integer getSecondsValid(){
        return secondsValid;
      }

      public void setSessionExpireTime(DateTime sessionExpireTime){
        this.sessionExpireTime = sessionExpireTime;
      }

      public DateTime getSessionExpireTime(){
        return sessionExpireTime;
      }

      public void setSessionValid(Boolean sessionValid){
        this.sessionValid = sessionValid;
      }

      public Boolean getSessionValid(){
        return sessionValid;
      }

      public void setLoginTime(DateTime loginTime){
        this.loginTime = loginTime;
      }

      public DateTime getLoginTime(){
        return loginTime;
      }
    }

Salesforce WSC Partner Connection Session Renew when Session Timeout

Implement SessionRenewer

package com.sforce.authentication;

import javax.inject.Inject;
import javax.xml.namespace.QName;


import org.slf4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

import com.sforce.async.AsyncApiException;
import com.sforce.async.BulkConnection;
import com.sforce.soap.partner.Connector;
import com.sforce.soap.partner.PartnerConnection;
import com.sforce.ws.ConnectionException;
import com.sforce.ws.ConnectorConfig;
import com.sforce.ws.SessionRenewer;

/**
 * @author tmichels
 */
@PropertySource("classpath:salesforcesync.properties")
@Configuration
public class SalesforceAuthenticationConfigImpl implements SalesforceAuthenticationConfig {

	private static final Logger logger = org.slf4j.LoggerFactory.getLogger(SalesforceAuthenticationConfigImpl.class);
	
	@Inject private Environment environment;
		    
	private PartnerConnection partnerConnection = null;
	private BulkConnection bulkConnection = null;
	
	public ConnectorConfig productionConnectionConfig(){
		ConnectorConfig config = new ConnectorConfig();
		config.setUsername("username");
		config.setPassword("passwordtoken");
		config.setCompression(true);
		return config;
	}
	
    @Bean(name="loginToProductionSalesforce")
	public PartnerConnection loginToProductionSalesforce() {
    	try {
    		ConnectorConfig config = productionConnectionConfig();
    		config.setSessionRenewer(new productionSessionRenewer());
        	partnerConnection = Connector.newConnection(config);
		} catch (ConnectionException e) {
			logger.error("SalesforceAuthenticationConfigImpl.loginToProductionSalesforce(): " + e.getMessage());
			e.printStackTrace();
		}
		return partnerConnection;
	}
    
    public class productionSessionRenewer implements SessionRenewer {
		@Override
		public SessionRenewalHeader renewSession(ConnectorConfig config) throws ConnectionException {
			ConnectorConfig conConfig = productionConnectionConfig();
			partnerConnection = Connector.newConnection(conConfig);
            SessionRenewalHeader header = new SessionRenewalHeader();
            header.name = new QName("urn:partner.soap.sforce.com", "SessionHeader");
            header.headerElement = partnerConnection.getSessionHeader();
            return header;
		}
    }
}

Test SessionRenewer

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes={SalesforceAuthenticationConfigImpl.class})
public class SalesforceAuthenticationServiceTest {

	@Inject @Named("loginToProductionSalesforce") PartnerConnection loginToProductionPartner;
	
	@Test
	public void testLoginToProductionSalesforce() {
		assertNotNull(loginToProductionPartner.getConfig().getSessionId());
		try {
			loginToProductionPartner.logout();
                        assertNotNull(loginToProductionPartner.getConfig().getSessionId());
		} catch (ConnectionException e) {
			e.printStackTrace();
		}
	}
}

Types of Java Beans

There are three types of beans in java:

1. Session Beans, it is also sub divide into two forms

Stateless Session beans

Stateful Session Beans

2. Entity Beans

3. Message Driven Beans or they are also called Message Beans.

Now I will explain them one by one:

Session Bean is created by a customer and its duration is only for the signal client server session. The function performed by this bean is calculations or database access, for the client. This bean can be transactional, because it is not recoverable and due to this may a system crash can occur. Its object is to manage its own unrelenting data.The stateless Session Beans is distributed object which has no connection with informal state; only allow parallel access to beans.Now the Stateful Session Bean has a connection with informal state, but its access is very limited to the customer.The second types are called Entity Bean and its function is to database the unrelenting data. It is recognized by a main key, if the container which is hosted by entity bean crashes, it will destroy the remote reference.Message bean is similar to session bean, except it responds to java message service.

Creating your first stateless session bean using RAD 7.5 running on WAS 7

Creating my first stateless session bean using Rational Application Developer 7.5 and deployed to Websphere Application Server V7.0.0.7 that shows the current system time.
Step 1: Create a new Enterprise Application Project.
Step 2: Create a new EJB Project by right clicking on the newly created Enterprise Application Project.
Make sure that to add the Enterprise Application Project as a EAR member.
Step 3: Right click on your newly created EJB project and select -> Session Bean
Enter the package name as: com.ibm.ejb.time
Enter the class name as: time
Make sure that business interface is set to Local
Step 4: Open the newly created Session Bean called time and add the following method.
Make sure that to add the Enterprise Application Project as a EAR member.
public String getTimeInfo(String timeinput) {
Calendar cal = Calendar.getInstance();
Date now = cal.getTime();
DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM);
if timeinput != null && timeinput.length() > 0) {
timeinput = timeinput.toUpperCase();
}
return DateFormat.getTimeInstance().format(now) + ” on ” + df.format(now);
Make sure to add all the neccessary Imports using Quick Fix.
Step 5: Right click anywhere in the editor and Navigate to Java EE Tool -> Promote Method
Select ‘getTimeInfo’ method and click ‘OK’
Step 6: Go back to your Enterprise Exlorer and right click on the Enterprise Application Project created in Step 1.
Select New -> Dynamic Web Project
Make sure that to add the Enterprise Application Project as a EAR member.
Step 7: Right click on your newly created Dynamic Web Project and select ‘Properties’
Step 8: Select ‘Java EE Module Dependencies’
Select ‘Allow Both’ under reference to EJB JARs with EJB client JARs.
Select both JARs and click ‘OK’
Step 9: Create a new Servlet by right clicking on the Dynamic Web Project and selecting New->Servlet.
Enter the package name as: com.ibm.ejb.time
Enter the class name as: timeServlet
Accept the superclass javax.servlet.http.HTTPServlet
Click Finish.
Step 10: Open the new timeServlet.java file.
Add the following two commands under TimeServlet class:
@EJB
TimeLocal time;
Using quick fix import the neccessary packages.
Add the following commands under doGet class:
PrintWriter out = response.getWriter();
String time = clock.getClockInfo(“medium”);
out.println(“<html><body>”);
out.println(“<h3>The current time is:</h3>”);
out.println(“<p>” + time + “</p>”);
out.println(“</body></html>”);
Using quick fix import the neccessary packages.
Add the following commands under doPost class:
String format = request.getParameter(“timeinput”);
request.setAttribute(“time”, clock.getTimeInfo(timeinput));
RequestDispatcher rd = getServletContext().
getRequestDispatcher(“/index.jsp”);
rd.forward(request, response);
return;
Step 11: Import the index.jsp file into your Dynamic Web Project.
Step 12: Start an instance of Websphere Application Server inside RAD 7.5
After the instance has started right click on the Server and click on ‘Add and Remove Projects’
Step 13: Run the Dynamic Web Project by right clicking on it and selecting ‘Run As’->’Run on Server’Creating my first stateless session bean using Rational Application Developer 7.5 and deployed to Websphere Application Server V7.0.0.7 that shows the current system time.