Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cache server results in GWT with guava?

In my GWT Application I'm often refering several times to the same server results. I also don't know which code is executed first. I therefore want to use caching of my asynchronous (client-side) results.

I want to use an existing caching library; I'm considering guava-gwt.

I found this example of a Guava synchronous cache (in guava's documentation):

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) throws AnyException {
               return createExpensiveGraph(key);
             }
           });

This is how I'm trying to use a Guava cache asynchronously (I have no clue about how to make this work):

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) throws AnyException {

               // I want to do something asynchronous here, I cannot use Thread.sleep in the browser/JavaScript environment.
               service.createExpensiveGraph(key, new AsyncCallback<Graph>() {

                 public void onFailure(Throwable caught) {
                   // how to tell the cache about the failure???
                 }

                 public void onSuccess(Graph result) {
                   // how to fill the cache with that result???
                 }
               });

               return // I cannot provide any result yet. What can I return???
             }
           });

GWT is missing many classes from the default JRE (especially concerning threads and concurrancy).

How can I use guava-gwt to cache asynchronous results?

like image 852
slartidan Avatar asked Jan 18 '16 09:01

slartidan


People also ask

Is Guava cache distributed?

(Guava caches are local to a single run of your application. They do not store data in files, or on outside servers. If this does not fit your needs, consider a tool like Memcached.)

Where is Guava cache stored?

Guava Caches store values in RAM.

What is Guava loading cache?

A semi-persistent mapping from keys to values. Values are automatically loaded by the cache, and are stored in the cache until either evicted or manually invalidated. Implementations of this interface are expected to be thread-safe, and can be safely accessed by multiple concurrent threads.


1 Answers

As I understood what you want to achieve is not just a asynchronous cache but also a lazy cache and to create one the GWT is not a best place as there is a big problem when implementing a GWT app with client side Asynchronous executions, as GWT lacks the client side implementations of Futures and/or Rx components (still there are some implementations of RxJava for GWT). So in usual java what you want to create can be achieved by :

LoadingCache<String, Future<String>> graphs = CacheBuilder.newBuilder().build(new CacheLoader<String, Future<String>>() {
    public Future<String> load(String key) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        return  service.submit(()->service.createExpensiveGraph(key));
    }
});
Future<String> value = graphs.get("Some Key");
if(value.isDone()){
    // This will block the execution until data is loaded 
    String success = value.get();           
}

But as GWT has no implementations for Futures you need to create one just like

public class FutureResult<T> implements AsyncCallback<T> {
    private enum State {
        SUCCEEDED, FAILED, INCOMPLETE;
    }

    private State state = State.INCOMPLETE;
    private LinkedHashSet<AsyncCallback<T>> listeners = new LinkedHashSet<AsyncCallback<T>>();
    private T value;
    private Throwable error;

    public T get() {
        switch (state) {
        case INCOMPLETE:
            // Do not block browser so just throw ex
            throw new IllegalStateException("The server response did not yet recieved.");
        case FAILED: {
            throw new IllegalStateException(error);
        }
        case SUCCEEDED:
            return value;
        }
        throw new IllegalStateException("Something very unclear");
    }

    public void addCallback(AsyncCallback<T> callback) {
       if (callback == null) return;
       listeners.add(callback);
    }

    public boolean isDone() {
        return state == State.SUCCEEDED;
    }

    public void onFailure(Throwable caught) {
        state = State.FAILED;
        error = caught;
        for (AsyncCallback<T> callback : listeners) {
            callback.onFailure(caught);
        }
    }

    public void onSuccess(T result) {
        this.value = result;
        state = State.SUCCEEDED;
        for (AsyncCallback<T> callback : listeners) {
            callback.onSuccess(value);
        }
    }

}

And your implementation will become :

    LoadingCache<String, FutureResult<String>> graphs = CacheBuilder.newBuilder().build(new CacheLoader<String, FutureResult<String>>() {
        public FutureResult<String> load(String key) {
            FutureResult<String> result = new FutureResult<String>();
            return service.createExpensiveGraph(key, result);
        }
    });

    FutureResult<String> value = graphs.get("Some Key");

    // add a custom handler
    value.addCallback(new AsyncCallback<String>() {
        public void onSuccess(String result) {
            // do something
        }
        public void onFailure(Throwable caught) {
            // do something             
        }
    });
    // or see if it is already loaded / do not wait 
    if (value.isDone()) {
        String success = value.get();
    }

When using the FutureResult you will not just cache the execution but also get some kind of laziness so you can show some loading screen while the data is loaded into cache.

like image 187
Babl Avatar answered Oct 26 '22 17:10

Babl