1package autotest.common; 2 3import com.google.gwt.core.client.GWT; 4import com.google.gwt.core.client.JavaScriptObject; 5import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; 6import com.google.gwt.dom.client.Element; 7import com.google.gwt.json.client.JSONObject; 8import com.google.gwt.user.client.Timer; 9 10import java.util.HashMap; 11import 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 */ 22public 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.@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