/* * Copyright 2011 ArcBees Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.gwtplatform.dispatch.rpc.client.interceptor.caching; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.gwt.user.client.rpc.AsyncCallback; import com.gwtplatform.dispatch.client.CompletedDispatchRequest; import com.gwtplatform.dispatch.client.ExecuteCommand; import com.gwtplatform.dispatch.rpc.client.CallbackDispatchRequest; import com.gwtplatform.dispatch.rpc.client.DefaultCallbackDispatchRequest; import com.gwtplatform.dispatch.rpc.client.DelegatingCallbackDispatchRequest; import com.gwtplatform.dispatch.rpc.client.interceptor.AbstractRpcInterceptor; import com.gwtplatform.dispatch.rpc.client.interceptor.UndoCommand; import com.gwtplatform.dispatch.rpc.shared.Action; import com.gwtplatform.dispatch.shared.DispatchRequest; /** * Abstract base class for client-side interceptors with caching support. * <p> * Supported features include: * </p> * <ol> * <li> * {@link #prefetch}/{@link #postfetch} perform the cache lookup and the cache store. You can use this * to customize the caching logic. * </li> * <li> * Automatic action queuing so that calls in quick succession result in a single trip to the server. * </li> * <li> * Flexibility of cache implementation to support custom caching * </li> * </ol> * * @param <A> The type of the action. * @param <R> The type of the result. */ public abstract class AbstractCachingRpcInterceptor<A, R> extends AbstractRpcInterceptor<A, R> { private final Cache cache; // Holds callbacks, so that for multiple requests before the first returns (is served), we save round trips as well private final Map<A, List<CallbackDispatchRequest<R>>> pendingRequestCallbackMap = new HashMap<>(); public AbstractCachingRpcInterceptor(Class<A> actionType, Cache cache) { super(actionType); this.cache = cache; } public DispatchRequest execute(final A action, final AsyncCallback<R> resultCallback, ExecuteCommand<A, AsyncCallback<R>> executeCommand) { // First check if any pending callbacks for this action List<CallbackDispatchRequest<R>> pendingRequestCallbacks = pendingRequestCallbackMap.get(action); if (pendingRequestCallbacks != null) { CallbackDispatchRequest<R> callbackDispatchRequest = new DefaultCallbackDispatchRequest<>(resultCallback); // Add callback to pending list and return pendingRequestCallbacks.add(callbackDispatchRequest); return callbackDispatchRequest; } // Prefetch to see if result is cached R prefetchResult = prefetch(action); if (prefetchResult != null) { // Return the cached result resultCallback.onSuccess(prefetchResult); return new CompletedDispatchRequest(); } else { // Execute DispatchRequest request = executeCommand.execute(action, new AsyncCallback<R>() { @Override public void onFailure(Throwable caught) { // Call postfetch with null result postfetch(action, null); resultCallback.onFailure(caught); // Callback onFailure List<CallbackDispatchRequest<R>> pendingRequestCallbacks = pendingRequestCallbackMap.remove(action); for (CallbackDispatchRequest<R> pendingRequestCallback : pendingRequestCallbacks) { if (pendingRequestCallback.isPending()) { pendingRequestCallback.onFailure(caught); } } } @Override public void onSuccess(R result) { // Postfetch postfetch(action, result); resultCallback.onSuccess(result); // Callback onSuccess List<CallbackDispatchRequest<R>> pendingRequestCallbacks = pendingRequestCallbackMap.remove(action); for (CallbackDispatchRequest<R> pendingRequestCallback : pendingRequestCallbacks) { if (pendingRequestCallback.isPending()) { pendingRequestCallback.onSuccess(result); } } } }); // Add pending callback ArrayList<CallbackDispatchRequest<R>> resultRequestCallbacks = new ArrayList<>(); CallbackDispatchRequest<R> callbackDispatchRequest = new DelegatingCallbackDispatchRequest<>(request, resultCallback); resultRequestCallbacks.add(callbackDispatchRequest); pendingRequestCallbackMap.put(action, resultRequestCallbacks); return callbackDispatchRequest; } } @Override public DispatchRequest undo(A action, R result, AsyncCallback<Void> callback, UndoCommand<A, R> undoCommand) { // Remove the cached entry getCache().remove(action); // Undo the previous action return undoCommand.undo(action, result, callback); } /** * Override this method to perform an action before the call is sent to the server. If the call returns a * non-{@code null} result then the action is never executed on the server and the returned value is used. If the * call returns {@code null} then the action is executed on the server. * <p/> * You can use this method to fetch the {@code action} from the cache. * * @param action The action to be prefetched * @return The prefetched result. If not found, return {@code null}. */ protected abstract R prefetch(A action); /** * Override this method to perform an action after the call to the server returns successfully or not. If the call * succeeded, the result will be passed, if it failed {@code null} will be passed in the {@code result} parameter. * <p/> * You can use this method to add the result to cache, if it is {@code null} you should remove the {@code action} * from the cache. * * @param action The action that just finished execution on the server. * @param result The result after the server call, or {@code null} if the server call failed. */ protected abstract void postfetch(A action, R result); /** * @return the cache */ protected Cache getCache() { return cache; } public boolean canExecute(Action<?> action) { return action.getClass().equals(getActionType()); } }