Home | History | Annotate | Download | only in common
      1 package autotest.common;
      2 
      3 import com.google.gwt.core.client.GWT;
      4 import com.google.gwt.core.client.JavaScriptObject;
      5 import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
      6 import com.google.gwt.dom.client.Element;
      7 import com.google.gwt.json.client.JSONObject;
      8 import com.google.gwt.user.client.Timer;
      9 
     10 import java.util.HashMap;
     11 import java.util.Map;
     12 
     13 /**
     14  * JsonRpcProxy that uses "JSON with Padding" (JSONP) to make requests.  This allows it to get
     15  * around the Same-Origin Policy that limits XmlHttpRequest-based techniques.  However, it requires
     16  * close coupling with the server and it allows the server to execute arbitrary JavaScript within
     17  * the page, so it should only be used with trusted servers.
     18  *
     19  * See http://code.google.com/docreader/#p=google-web-toolkit-doc-1-5&s=google-web-toolkit-doc-1-5&t=Article_UsingGWTForJSONMashups.
     20  * Much of the code here is borrowed from or inspired by that article.
     21  */
     22 public class PaddedJsonRpcProxy extends JsonRpcProxy {
     23     private static final int REQUEST_TIMEOUT_MILLIS = 60000;
     24     private static final String SCRIPT_TAG_PREFIX = "__jsonp_rpc_script";
     25     private static final String CALLBACK_PREFIX = "__jsonp_rpc_callback";
     26 
     27     private static int idCounter = 0;
     28 
     29     private String rpcUrl;
     30 
     31     private static class JsonpRequest {
     32         private int requestId;
     33         private String requestData;
     34         private Element scriptTag;
     35         private String callbackName;
     36         private Timer timeoutTimer;
     37         private JsonRpcCallback rpcCallback;
     38         private boolean timedOut = false;
     39 
     40         public JsonpRequest(String requestData, JsonRpcCallback rpcCallback) {
     41             requestId = getRequestId();
     42             this.requestData = requestData;
     43             this.rpcCallback = rpcCallback;
     44 
     45             callbackName = CALLBACK_PREFIX + requestId;
     46             addCallback(this, callbackName);
     47 
     48             timeoutTimer = new Timer() {
     49                 @Override
     50                 public void run() {
     51                     timedOut = true;
     52                     cleanup();
     53                     notify.showError("Request timed out");
     54                     JsonpRequest.this.rpcCallback.onError(null);
     55                 }
     56             };
     57         }
     58 
     59         private String getFullUrl(String rpcUrl) {
     60             Map<String, String> arguments = new HashMap<String, String>();
     61             arguments.put("callback", callbackName);
     62             arguments.put("request", requestData);
     63             return rpcUrl + "?" + Utils.encodeUrlArguments(arguments);
     64         }
     65 
     66         public void send(String rpcUrl) {
     67             scriptTag = addScript(getFullUrl(rpcUrl), requestId);
     68             timeoutTimer.schedule(REQUEST_TIMEOUT_MILLIS);
     69             notify.setLoading(true);
     70         }
     71 
     72         public void cleanup() {
     73             dropScript(scriptTag);
     74             dropCallback(callbackName);
     75             timeoutTimer.cancel();
     76             notify.setLoading(false);
     77         }
     78 
     79         /**
     80          * This method is called directly from native code (the dynamically loaded <script> calls
     81          * our callback method, which calls this), so we need to do proper GWT exception handling
     82          * manually.
     83          *
     84          * See the implementation of com.google.gwt.user.client.Timer.fire(), from which this
     85          * technique was borrowed.
     86          */
     87         @SuppressWarnings("unused")
     88         public void handleResponse(JavaScriptObject responseJso) {
     89             UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
     90             if (handler == null) {
     91                 handleResponseImpl(responseJso);
     92                 return;
     93             }
     94 
     95             try {
     96                 handleResponseImpl(responseJso);
     97             } catch (Throwable throwable) {
     98                 handler.onUncaughtException(throwable);
     99             }
    100         }
    101 
    102         public void handleResponseImpl(JavaScriptObject responseJso) {
    103             cleanup();
    104             if (timedOut) {
    105                 return;
    106             }
    107 
    108             JSONObject responseObject = new JSONObject(responseJso);
    109             handleResponseObject(responseObject, rpcCallback);
    110         }
    111     }
    112 
    113     public PaddedJsonRpcProxy(String rpcUrl) {
    114         this.rpcUrl = rpcUrl;
    115     }
    116 
    117     private static int getRequestId() {
    118         return idCounter++;
    119     }
    120 
    121     private static native void addCallback(JsonpRequest request, String callbackName) /*-{
    122         window[callbackName] = function(someData) {
    123             request. (at) autotest.common.PaddedJsonRpcProxy.JsonpRequest::handleResponse(Lcom/google/gwt/core/client/JavaScriptObject;)(someData);
    124         }
    125     }-*/;
    126 
    127     private static native void dropCallback(String callbackName) /*-{
    128         delete window[callbackName];
    129     }-*/;
    130 
    131     private static Element addScript(String url, int requestId) {
    132         String scriptId = SCRIPT_TAG_PREFIX + requestId;
    133         Element scriptElement = addScriptToDocument(scriptId, url);
    134         return scriptElement;
    135     }
    136 
    137     private static native Element addScriptToDocument(String uniqueId, String url) /*-{
    138         var elem = document.createElement("script");
    139         elem.setAttribute("language", "JavaScript");
    140         elem.setAttribute("src", url);
    141         elem.setAttribute("id", uniqueId);
    142         document.getElementsByTagName("body")[0].appendChild(elem);
    143         return elem;
    144     }-*/;
    145 
    146     private static native void dropScript(Element scriptElement) /*-{
    147         document.getElementsByTagName("body")[0].removeChild(scriptElement);
    148     }-*/;
    149 
    150     @Override
    151     protected void sendRequest(JSONObject request, JsonRpcCallback callback) {
    152         JsonpRequest jsonpRequest = new JsonpRequest(request.toString(), callback);
    153         jsonpRequest.send(rpcUrl);
    154     }
    155 }
    156