XPages Data Caching and a Dive Into Java
Wed Jul 02 16:39:54 EDT 2014
One of the most common idioms I run into when writing an app is the desire to cache some expensive-to-compute data in the view or application scope on first fetch, and then use the cached version from then on. It usually takes a form like this:
public Date getCachedTime() { Map<String, Object> applicationScope = ExtLibUtil.getApplicationScope(); if(!applicationScope.containsKey("cachedTime")) { // Some actual expensive operation goes here applicationScope.put("cachedTime", new Date()); } return (Date)applicationScope.get("cachedTime"); }
That works well, but it bothered me that I had to write the same boilerplate code every time, and I wondered if I could come up with a better way. The short answer is "no, not really". But the longer answer is "sort of!". And though the solution I came up with is kinda pathological and is generally unsuitable for normal humans and not worth the thin advantages, it does provide a good example of a couple of more-advanced Java concepts that you're likely to run across the more you get into the language. So here's the cache function in question, plus a "shorthand" version and an example:
@SuppressWarnings("unchecked") public static <T> T cache(final String cacheKey, final String scope, final Callable<T> callable) throws Exception { Map<String, Object> cacheScope = (Map<String, Object>) ExtLibUtil.resolveVariable(FacesContext.getCurrentInstance(), scope + "Scope"); if (!cacheScope.containsKey("cacheMap")) { cacheScope.put("cacheMap", new HashMap()); } Map<String, Object> cacheMap = (Map<String, Object>) cacheScope.get("cacheMap"); if (!cacheMap.containsKey(cacheKey)) { cacheMap.put(cacheKey, callable.call()); } return (T) cacheMap.get(cacheKey); } public static <T> T cache(final String cacheKey, final Callable<T> callable) throws Exception { return cache(cacheKey, "application", callable); } public Date getFetchedTime() throws Exception { return cache("fetchedTime", new Callable<Date>() { public Date call() throws Exception { return new Date(); } }); }
This code begs a question: what in the name of all that is good and holy is going on? There are a number of Java concepts at work here, some of which you've likely already seen:
- Generics. Generics are the class names in angle brackets, like
Map<String, Object>
andCallable<Date>
. They're Java's way of taking a container or producer class and making it usable with any class types without having to cast everything from Object. - Overriding methods. This is a small one: you can declare the same method name with different parameter types, which is often useful for "shorthand" versions of methods that allow for default values after a fashion. In this case, the short version of "cache" defaults to using the application scope by name.
- The
Callable
interface. This is an interface in thejava.util.concurrent
package and is meant as a utility for multithreading purposes, but it also serves our needs here. Basically, it's an interface that means "this contains code that can be executed by using thecall
method with no arguments". - Anonymous classes. This is that business with "
new Callable...
". Java calls those anonymous classes and what they are are classes declared and instantiated inline with the code, without having a "proper" class declaration somewhere else. They're called "anonymous" because they have no name of their own - just the interface or class they implement/extend. In this case, I'm saying "make a new object that implementsCallable
and is used for this purpose only". I could make a standalone class, but there's no need. If you're familiar with closures from good languages, anonymous classes can be used like a really crappy version of those. - Generic return value declarations. The "
<T> T
" and "Callable<T>
" bits build on normal generic use for when declaring a method. The first one, is brackets, basically says "okay Java, rather than returning a known object type, I'm going to let the user use anything, and for this purpose we're going to call itT
". So from then on, an occurrence ofT
in that method body refers to whatever the programmer using the method intends. You can see in thegetFetchedTime
that I'm using aCallable<Date>
; Java picks up on that "Date
" name and conceptually substitutes it for all theT
s in the method. I'm essentially saying "I'm going to use thecache
method, and I want it to accept and returnDate
s". Another call to the method could be to cache aList<String>
or aSomeUserClass
, while the method itself would remain the same.
So is all this worth it for the task at hand? Probably not. There are SOME advantages, in that it's easy to change the default caching scope, and in practice I also used a map that auto-expires its entries over time, but for normal use the first idiom is fine. But hey, it sure provided a whole torrent of Java concepts, so that's worth something.
Stephan H. Wissel - Fri Jul 04 23:35:47 EDT 2014
Nice one. Love the explanation of the concepts at work. Caching is a hot topic and a slippery road. On