Saturday, August 22, 2009

Using memcache for Session Beans in GAE

Continued from Why Use JSP for GWT RPC Server-side Service

Note: the statement "JSP session beans are not supported in GAE" is outdated. Nonetheless, the examples on using memcache still holds. Session beans can be enabled in the appengine-web.xml:

<?xml version="1.0" encoding="utf-8"?>

[outdate info]
JSP session beans are not supported in GAE. The jsp:usebean tag has no effect of value in a JSP running in GAE. As session control is not implemented in GAE, we would need to exploit persistence features of GAE to implement it. A strategy would need to be employed to put and get session beans. The tactic described here uses memcache, which is implemented by GAE as standard Java caching API (import javax.cache.*).
[/oudated info]

GAE memcache is not actually session cache by itself because the stored blobs do not actually expire. We have to deliberately set an expiry time.

SessionSilo, below, can be used as a template for implementing session persistence of beans. Please refer to synthful project in Google Code for its complete source code. Here is the outline of the strategy.
  1. At start of session initialise persistence cache.
  2. Set an expiry time for the cache.
  3. At the first JSP response within a session, declare the bean.
  4. At every start of JSP response, get the session bean from persistence cache.
  5. At end of JSP response, if the bean had updates, put the bean into persistence cache, overwriting its old blob stored in the cache.

package com.blessedgeek.gwt.gdata.server;

public class SessionSilo

/** * Create new instance of static cache, beanCache. * GAE memcache is configurable thro certain reserved keys. * A reserved key and its value can be injected into the * cache thro a hashmap. * If a config hashmap is not injected into the cache, * the cache would take on default values. * */ static public void initBeanCache(){ if (beanCache!=null) return; Map cfgMap = new HashMap(); cfgMap.put(GCacheFactory.EXPIRATION_DELTA, 900); try{ CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory(); beanCache = cacheFactory.createCache(cfgMap); } catch (CacheException e){} }
/** * Call this at start of a JSP to get the session bean * If bean does not exist, create and put it in the cache. */ static public MrBean initSessionBean(String sessId){ MrBean mrBean = getBean(sessId); if (mrBean==null){ mrBean = new MrBean(); mrBean.sessionId = sessId; putBean(sessId, mrBean); } return mrBean; }
/** * Call this at the end of a JSP. * If any routine updates the bean, * those routines would need to set updated flag to true. * If updated is true, then put the bean back into cache * to over-write its existing blob in the cache. * * @param mrBean */ static public void storeSessionBean(MrBean mrBean){ if(mrBean.isUpdated()) putBean(mrBean.sessionId, mrBean); }
static Cache beanCache; }

Example of JSP using SessionSilo static methods to store and get "session" beans.
page language="java"
MrBean mrBean = SessionSilo.initSessionBean(session.getId());


In the TableMgr application, TableActionService.gwtrpc.jsp employs this technique.

Its subsidiary JSPs conditionally called-by-include also have to employ these series of manoeuvres because GAE does not pass bean instances across an include call: Listxxx.jsp, Loggedin.jsp.

Remember that a bean being cached must implement the java.util.serializable interface. Every member class of the bean as well as the whole depth of recursive members must also implement serializable. Otherwise, GAE will dump the response due to encountering non-serializable exception when attempting to put the bean into memcache.

If a bean has many members, it may be better to cache individual members of the bean. Why should a whole cached blob be replaced if only one tiny bit was changed for each response? Caching individual members of a bean also allows a bean to have non-serializable members that do not need persistence.

You should also read