11.2. Cache
The next task is to cache the symbols in App Engine MemCache. By moving
the symbols from List<Symbol> in UploadService
servlet to MemCache,
they become accessible to App Engine services like Queue, Tasks etc.
CachePanel
This widget has two Buttons - Cache Symbols and Clear cache, with obvious functionality.
Figure 11.3. CachePanel
Its design is similar to the UploadPanel
described in the previous
section, and it uses FormPanel
to submit data to the server and
invokes UploadService
servlet, through regular HTML form submission
rather than GWT RPC.
CachePanel
is added to DataStore
content page, in its UI declaration
file.
in.fins.client.content/DataStore.ui.xml
....
<g:VerticalPanel spacing="40">
<f:UploadPanel />
<f:CachePanel />
</g:VerticalPanel>
</g:layer>
</g:LayoutPanel>
</ui:UiBinder>
Cache
App Engine provides distributed in-memory data cache service known as MemCache, and high performance web applications ofter use them to reduce datastore access. App Engine Cache API comes in two flavors - JCache and Low Level API. JCache provides a map-like interface to cache where data is stored and retrieved using keys where value is any Serializable type or class. Low Level API is richer and provides MemcacheService and AsyncMemcacheService. By default, data is retained by cache as long as possible and evicted only when it is low on memory. Maximum size of each cached object is l MB.
We use cache to hold Symbols and for this, JCache API is sufficient. But, Fins should be able to use the cache even when it runs on a regular app server like JBoss. JCache is based on, yet to be official, JSR 107 proposal and App Engine uses one of its implementation, net.sf.jsr107cache. We encountered some issues in using this interface in non App Engine environment, so decided to move the cache operations behind our own interface, which enable us to switch cache while moving out of App Engine. For non App Engine setup, we have the option to use industry standard cache modules like Ehcache etc., but for our limited use of cache, it is a overkill and hence, we are going to use a simple custom cache.
Following figure shows the cache interaction by UploadService
.
Figure 11.4. Cache interface
Interactions between UploadService
and cache happens through interface
ICache
, which defines the methods to interact with the cache. Both; GaeCache
used in App Engine, and SimpleCache
used in regular app
server, implements ICache
.
in.fins.server.cache/ICache.java
public interface ICache {
public void createCache() throws Exception;
public void put(String key, Object value);
public Object get(String key);
public void clear();
}
ICache provide access to cache through a map-like interface, where
method put()
stores key and value in the cache, and method get()
retrieves the value object for a key.
GaeCache
obtains reference to MemCache through, CacheManager
and
CacheFactory
.
in.fins.server.cache/GaeCache.java
public class GaeCache implements ICache {
private Cache cache;
@Override
public void createCache() throws Exception {
try {
if (cache == null) {
CacheFactory cacheFactory =
CacheManager.getInstance() .getCacheFactory();
cache = cacheFactory.createCache(Collections.emptyMap());
}
} catch (CacheException e) {
log.warning(e.getMessage());
throw e;
}
}
SimpleCache
, which used in regular app server setup, uses a singleton
class Cache
which internally uses a HashMap<String,Object> to hold
cache data. Cache is is a non public class of SimpleCache.
in.fins.server.cache/GaeCache.java
public class SimpleCache implements ICache {
private Cache cache;
@Override
public void createCache() throws Exception {
cache = Cache.INSTANCE;
}
@Override
public void put(String key, Object value) {
cache.put(key, value);
}
....
}
// Singleton Cache
class Cache {
public static final Cache INSTANCE = new Cache();
private Map<String, Object> cache;
private Cache() {
cache = new HashMap<String, Object>();
}
public void put(String key, Object value) {
cache.put(key, value);
}
....
}
UploadService
uses cacheSymbols()
and clearSymbols()
methods to
handle user requests on form submission. These methods create
appropriate cache based on the platform on which app is running.
in.fins.server.cache/GaeCache.java
....
private String cacheSymbols() throws Exception {
String serverName = getServerName();
ICache cache = null;
if (serverName.equals("GAE")) {
cache = Utils.createInstance(ICache.class,
"in.fins.server.cache.GaeCache");
} else {
cache = Utils.createInstance(ICache.class,
"in.fins.server.cache.SimpleCache");
}
cache.createCache();
....
}
private String getServerName() {
String serverName = getServletContext().getServerInfo();
if (serverName.startsWith("Google App Engine")) {
return "GAE";
} else {
return serverName;
}
}
Depending on the platform name returned by getServerName()
method,
cacheSymbols()
obtains an instance of either GaeCache
or
SimpleCache
, through helper method Utils.createInstance()
. The util
method creates an instance based on the class name string, and this
ensures that UploadService compiles, even when App Engine related
libraries are not available in project class path, which is normally the
case when we use the Fins in regular app server setup.
Once cache is obtained, cacheSymbols()
method iterates through
List<Symbol> and cache the symbol with symbol name as key. While doing
so, it also creates and cache a list of symbol names, which is later
used as key set.
in.fins.server.cache/GaeCache.java
List<String> symbolNames = new ArrayList<String>();
for (Symbol symbol : symbols) {
cache.put(symbol.getName(), symbol);
symbolNames.add(symbol.getName());
}
cache.put("symbolNames", symbolNames);
int cacheCount = 0;
// check cache consistency
for (String name : symbolNames) {
Object o = cache.get(name);
if (o instanceof Symbol) {
cacheCount++;
}
}
if (cacheCount != symbolNames.size()) {
status +=
"Cache inconsistency. Try this option again to rectify.";
}
return status;
For some unknown reasons, occasionally, we could not find the some of the symbols in cache even though they were handed over to the cache. In such cases, inconsistency is reported back to the user and invoking the option again usually rectify the inconsistency.
Once symbols are in cache, we can go ahead with the task of persisting them to App Engine’s datastore.