I am trying to enable the data cache for one of my GWT widgets.
I have a datasource interface / class that retrieves some data from my backend via RequestBuilder
and JSON. Since I show the widget several times, I want to get the data only once.
So, I tried to connect to the application cache. The naive approach is to use a HashMap
in one object to store data. However, I also want to use HTML5 localStorage / sessionStorage if supported.
HTML5 localStorage only supports String values. So I have to convert my object to JSON and save as a string. However, somehow I can't come up with a good clean way to do this. that's what i still have.
I define an interface with two functions: fetchStatsList()
selects a list of statistics that can be displayed in widgets, and fetchStatsData()
retrieves the actual data.
public interface DataSource { public void fetchStatsData(Stat stat,FetchStatsDataCallback callback); public void fetchStatsList(FetchStatsListCallback callback); }
The Stat
class is a simple Javascript Overlay ( JavaScriptObject
) class with some getters (getName (), etc.). I have a regular implementation without caching RequestBuilderDataSource
my DataSource, which looks like this:
public class RequestBuilderDataSource implements DataSource { @Override public void fetchStatsList(final FetchStatsListCallback callback) {
I left most of the code for RequestBuilder, as it is quite simple.
This works out of the box, however, a list of statistics as well as data is retrieved each time, even hard data is distributed between each instance of the widget.
To support caching, I add a Cache
interface and two Cache implementations (one for HTML5 localStorage and one for HashMap):
public interface Cache { void put(Object key, Object value); Object get(Object key); void remove(Object key); void clear(); }
I am adding a new RequestBuilderCacheDataSource
class that extends RequestBuilderDataSource
and takes a Cache
instance in its constructor.
public class RequestBuilderCacheDataSource extends RequestBuilderDataSource { private final Cache cache; publlic RequestBuilderCacheDataSource(final Cache cache) { this.cache = cache; } @Override public void fetchStatsList(final FetchStatsListCallback callback) { Object value = cache.get("list"); if (value != null) { callback.fetchStatsList((List<Stat>)value); } else { super.fetchStatsList(stats,new FetchStatsListCallback() { @Override public void onFetchStatsList(List<Stat>stats) { cache.put("list",stats); callback.onFetchStatsList(stats); } }); super.fetchStatsList(callback); } } @Override public void fetchStatsData(List<Stat> stats,final FetchStatsDataCallback callback) { String url = getStatUrl(stats); Object value = cache.get(url); if (value != null) { callback.onFetchStatsData((DataTable)value); } else { super.fetchStatsData(stats,new FetchStatsDataCallback() { @Override public void onFetchStatsData(DataTable dataTable) { cache.put(url,dataTable); callback.onFetchStatsData(dataTable); } }); } } }
Basically, the new class will look up the value in Cache
, and if it is not found, it will call the select function in the parent class and intercept the callback to put it in the cache, and then call the actual callback. <w> Therefore, in order to support both HTML5 localstorage and regular JS HashMap memory, I created two implementations of my Cache
interface:
JS HashMap Storage :
public class DefaultcacheImpl implements Cache { private HashMap<Object, Object> map; public DefaultCacheImpl() { this.map = new HashMap<Object, Object>(); } @Override public void put(Object key, Object value) { if (key == null) { throw new NullPointerException("key is null"); } if (value == null) { throw new NullPointerException("value is null"); } map.put(key, value); } @Override public Object get(Object key) {
HTML5 localStorage :
public class LocalStorageImpl implements Cache{ public static enum TYPE {LOCAL,SESSION} private TYPE type; private Storage cacheStorage = null; public LocalStorageImpl(TYPE type) throws Exception { this.type = type; if (type == TYPE.LOCAL) { cacheStorage = Storage.getLocalStorageIfSupported(); } else { cacheStorage = Storage.getSessionStorageIfSupported(); } if (cacheStorage == null) { throw new Exception("LocalStorage not supported"); } } @Override public void put(Object key, Object value) {
The message was a little long, but I hope it clarifies what I'm trying to get to. It comes down to two questions:
- Feedback on the design / architecture of this approach (e.g. subclassing RequestBilderDataSource for cache function, etc.). Could this be improved (probably this has more to do with the overall design than with the GWT).
- Using
DefaultCacheImpl
very easy to store and retrieve any arbitrary objects. How can I do the same with localStorage where I need to convert and parse JSON? I am using DataTable
, which requires calling the DataTable.create(JavaScriptObject jso)
function to work. How can I solve this without using many if / else and instances of checks?