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.

CachePanel
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.

Cache interface
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.