• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
JsonpRequest(String requestData, JsonRpcCallback rpcCallback)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 
getFullUrl(String rpcUrl)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 
send(String rpcUrl)66         public void send(String rpcUrl) {
67             scriptTag = addScript(getFullUrl(rpcUrl), requestId);
68             timeoutTimer.schedule(REQUEST_TIMEOUT_MILLIS);
69             notify.setLoading(true);
70         }
71 
cleanup()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")
handleResponse(JavaScriptObject responseJso)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 
handleResponseImpl(JavaScriptObject responseJso)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 
PaddedJsonRpcProxy(String rpcUrl)113     public PaddedJsonRpcProxy(String rpcUrl) {
114         this.rpcUrl = rpcUrl;
115     }
116 
getRequestId()117     private static int getRequestId() {
118         return idCounter++;
119     }
120 
addCallback(JsonpRequest request, String callbackName)121     private static native void addCallback(JsonpRequest request, String callbackName) /*-{
122         window[callbackName] = function(someData) {
123             request.@autotest.common.PaddedJsonRpcProxy.JsonpRequest::handleResponse(Lcom/google/gwt/core/client/JavaScriptObject;)(someData);
124         }
125     }-*/;
126 
dropCallback(String callbackName)127     private static native void dropCallback(String callbackName) /*-{
128         delete window[callbackName];
129     }-*/;
130 
addScript(String url, int requestId)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 
addScriptToDocument(String uniqueId, String url)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 
dropScript(Element scriptElement)146     private static native void dropScript(Element scriptElement) /*-{
147         document.getElementsByTagName("body")[0].removeChild(scriptElement);
148     }-*/;
149 
150     @Override
sendRequest(JSONObject request, JsonRpcCallback callback)151     protected void sendRequest(JSONObject request, JsonRpcCallback callback) {
152         JsonpRequest jsonpRequest = new JsonpRequest(request.toString(), callback);
153         jsonpRequest.send(rpcUrl);
154     }
155 }
156