Кэшировать в приложении / виджете GWT с помощью HTML5 localStorage

Я пытаюсь включить кэш данных для одного из моих виджетов GWT.
У меня есть интерфейс / класс источника данных, который извлекает некоторые данные из моего бэкэнда черезRequestBuilder и JSON. Поскольку я отображаю виджет несколько раз, я хочу получить данные только один раз.

Поэтому я попытался зайти с кешем приложений. Наивный подход заключается в использованииHashMap в одноэлементном объекте для хранения данных. Однако я также хочу использовать localStorage / sessionStorage HTML5, если это поддерживается.
HTML5 localStorage поддерживает только строковые значения. Поэтому я должен преобразовать свой объект в JSON и сохранить в виде строки. Однако, так или иначе, я не могу придумать хороший чистый способ сделать это. вот что у меня так далеко.

Я определяю интерфейс с двумя функциями:fetchStatsList() извлекает список статистики, которая может отображаться в виджете, иfetchStatsData() извлекает фактические данные.

public interface DataSource {
    public void fetchStatsData(Stat stat,FetchStatsDataCallback callback);
    public void fetchStatsList(FetchStatsListCallback callback);
}

Stat класс является простым классом наложения Javascript (JavaScriptObject) с некоторыми получателями (getName () и т. д.) У меня есть нормальная реализация без кэшированияRequestBuilderDataSource моего источника данных, который выглядит следующим образом:

public class RequestBuilderDataSource implements DataSource {
    @Override
    public void fetchStatsList(final FetchStatsListCallback callback) {
         // create RequestBuilderRequest, retrieve response and parse JSON 
        callback.onFetchStatsList(stats);
    }

    @Override
    public void fetchStatsData(List<Stat> stats,final FetchStatsDataCallback callback) {
        String url = getStatUrl(stats);
        //create RequestBuilderRquest, retrieve response and parse JSON
        callback.onFetchStats(dataTable); //dataTable is of type DataTable
    }
}

Я пропустил большую часть кода для RequestBuilder, поскольку он довольно прост.

Это работает "из коробки", однако список статистики, а также данные извлекаются каждый раз, даже если данные распределяются между каждым экземпляром виджета.

Для поддержки кэширования я добавляюCache интерфейс и две реализации Cache (одна для HTML5 localStorage и одна для HashMap):

public interface Cache {
    void put(Object key, Object value);
    Object get(Object key);
    void remove(Object key);
    void clear();
}

Я добавляю новый классRequestBuilderCacheDataSource который расширяетRequestBuilderDataSource и беретCache экземпляр в своем конструкторе.

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);
                }
            });
        }
    }
}

В основном новый класс будет искать значение вCache и если он не найден, он вызовет функцию выборки в родительском классе и перехватит обратный вызов, чтобы поместить его в кэш, а затем вызовет реальный обратный вызов.
Поэтому для поддержки как локального хранилища HTML5, так и обычного хранилища JS HashMap я создал две реализации моегоCache интерфейс:

Хранилище JS HashMap:

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) {
        // Check for null as Cache should not store null values / keys
        if (key == null) {
            throw new NullPointerException("key is null");
        }
        return map.get(key);
    }

    @Override
    public void remove(Object key) {
        map.remove(key);
    }

    @Override
    public void clear() {
       map.clear();
    }
}

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) {
        //Convert Object (could be any arbitrary object) into JSON
        String jsonData = null;
        if (value instanceof List) {   // in case it is a list of Stat objects
            JSONArray array = new JSONArray();
            int index = 0;
            for (Object val:(List)value) {
                array.set(index,new JSONObject((JavaScriptObject)val));
                index = index +1;
            }
            jsonData = array.toString();
        }
        else  // in case it is a DataTable
        {
            jsonData = new JSONObject((JavaScriptObject) value).toString();
        }
        cacheStorage.setItem(key.toString(), jsonData);
    }

    @Override
    public Object get(Object key) {
        if (key == null) {
            throw new NullPointerException("key is null");
        }
        String jsonDataString = cacheStorage.getItem(key.toString());
        if (jsonDataString == null) {
            return null;
        }
        Object data = null;
        Object jsonData = JsonUtils.safeEval(jsonDataString);
        if (!key.equals("list")) 
            data = DataTable.create((JavaScriptObject)data);
        else if (jsonData instanceof JsArray){
            JsArray<GenomeStat> jsonStats = (JsArray<GenomeStat>)jsonData;
            List<GenomeStat> stats = new ArrayList<GenomeStat>();
            for (int i = 0;i<jsonStats.length();i++) {
                stats.add(jsonStats.get(i));
            }
            data = (Object)stats;
        }
        return data;
    }

    @Override
    public void remove(Object key) {
        cacheStorage.removeItem(key.toString());
    }

    @Override
    public void clear() {
        cacheStorage.clear();
    }

    public TYPE getType() {
        return type;
    }
}

Пост стал немного длиннее, но, надеюсь, проясняет, чего я пытаюсь достичь. Это сводится к двум вопросам:

Отзывы о дизайне / архитектуре этого подхода (например, создание подкласса RequestBilderDataSource для функции кэширования и т. Д.). Может ли это быть улучшено (это, вероятно, больше связано с общим дизайном, чем конкретно с GWT).СDefaultCacheImpl это действительно легко хранить и извлекать любые произвольные объекты. Как я могу добиться того же с помощью localStorage, где мне нужно конвертировать и анализировать JSON? Я используюDataTable который требует вызватьDataTable.create(JavaScriptObject jso) функция для работы. Как я могу решить эту проблему без многих if / else и экземпляров проверок?

Ответы на вопрос(1)

Ваш ответ на вопрос