Add JSONP support - resubmit of 5208 after fixing IE7 bug.
Patch by: dwolf, jat
Review by: rjrjr, jgw (verbal)
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5225 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/jsonp/Jsonp.gwt.xml b/user/src/com/google/gwt/jsonp/Jsonp.gwt.xml
new file mode 100644
index 0000000..fd39299
--- /dev/null
+++ b/user/src/com/google/gwt/jsonp/Jsonp.gwt.xml
@@ -0,0 +1,18 @@
+<!-- -->
+<!-- Copyright 2009 Google 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 -->
+<!-- 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. License for the specific language governing permissions and -->
+<!-- limitations under the License. -->
+
+<module>
+ <inherits name='com.google.gwt.user.User'/>
+ <source path="client"/>
+</module>
diff --git a/user/src/com/google/gwt/jsonp/client/JsonpRequest.java b/user/src/com/google/gwt/jsonp/client/JsonpRequest.java
new file mode 100644
index 0000000..62a8eb0
--- /dev/null
+++ b/user/src/com/google/gwt/jsonp/client/JsonpRequest.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2009 Google 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.google.gwt.jsonp.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.ScriptElement;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * A JSONP request that is waiting for a response. The request can be cancelled.
+ *
+ * @param <T> the type of the response object.
+ */
+public class JsonpRequest<T> {
+
+ /**
+ * Each request will be assigned a new id.
+ */
+ private static int callbackCounter = 0;
+
+ /**
+ * __jsonp__ is a global object that contains callbacks of pending requests.
+ */
+ private static final String CALLBACKS_NAME = "__gwt_jsonp__";
+ private static final JavaScriptObject CALLBACKS = createCallbacksObject(CALLBACKS_NAME);
+
+ private static native Node getDocumentElement() /*-{
+ return $doc.documentElement;
+ }-*/;
+
+ private static String nextCallbackId() {
+ return "I" + (callbackCounter++);
+ }
+
+ private final String callbackId;
+
+ private final int timeout;
+
+ private final AsyncCallback<T> callback;
+
+ /**
+ * Whether the result is expected to be an integer or not
+ */
+ @SuppressWarnings("unused") // used by JSNI
+ private final boolean expectInteger;
+
+ private final String callbackParam;
+
+ private final String failureCallbackParam;
+
+ /**
+ * Timer which keeps track of timeouts.
+ */
+ private Timer timer;
+
+ /**
+ * Creates a global object to store callbacks of pending requests.
+ *
+ * @param name The name of the global object.
+ * @return The created object.
+ */
+ private static native JavaScriptObject createCallbacksObject(String name) /*-{
+ return $wnd[name] = new Object();
+ }-*/;
+
+ /**
+ * Create a new JSONP request.
+ *
+ * @param callback The callback instance to notify when the response comes
+ * back
+ * @param timeout Time in ms after which a {@link TimeoutException} will be
+ * thrown
+ * @param expectInteger Should be true if T is {@link Integer}, false
+ * otherwise
+ * @param callbackParam Name of the url param of the callback function name
+ * @param failureCallbackParam Name of the url param containing the the
+ * failure callback function name, or null for no failure callback
+ */
+ JsonpRequest(AsyncCallback<T> callback, int timeout, boolean expectInteger,
+ String callbackParam, String failureCallbackParam) {
+ callbackId = nextCallbackId();
+ this.callback = callback;
+ this.timeout = timeout;
+ this.expectInteger = expectInteger;
+ this.callbackParam = callbackParam;
+ this.failureCallbackParam = failureCallbackParam;
+ }
+
+ /**
+ * Cancels a pending request.
+ */
+ public void cancel() {
+ timer.cancel();
+ unload();
+ }
+
+ public AsyncCallback<T> getCallback() {
+ return callback;
+ }
+
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Sends a request using the JSONP mechanism.
+ *
+ * @param baseUri To be sent to the server.
+ */
+ void send(final String baseUri) {
+ registerCallbacks(CALLBACKS);
+ StringBuffer uri = new StringBuffer(baseUri);
+ uri.append(baseUri.contains("?") ? "&" : "?");
+ String prefix = CALLBACKS_NAME + "." + callbackId;
+
+ uri.append(callbackParam).append("=").append(prefix).append(
+ ".onSuccess");
+ if (failureCallbackParam != null) {
+ uri.append("&");
+ uri.append(failureCallbackParam).append("=").append(prefix).append(
+ ".onFailure");
+ }
+ ScriptElement script = Document.get().createScriptElement();
+ script.setType("text/javascript");
+ script.setId(callbackId);
+ script.setSrc(uri.toString());
+ getDocumentElement().getFirstChild().appendChild(script);
+ timer = new Timer() {
+ @Override
+ public void run() {
+ onFailure(new TimeoutException("Timeout while calling " + baseUri));
+ }
+ };
+ timer.schedule(timeout);
+ }
+
+ @SuppressWarnings("unused") // used by JSNI
+ private void onFailure(String message) {
+ onFailure(new Exception(message));
+ }
+
+ private void onFailure(Throwable ex) {
+ timer.cancel();
+ try {
+ if (callback != null) {
+ callback.onFailure(ex);
+ }
+ } finally {
+ unload();
+ }
+ }
+
+ @SuppressWarnings("unused") // used by JSNI
+ private void onSuccess(T data) {
+ timer.cancel();
+ try {
+ if (callback != null) {
+ callback.onSuccess(data);
+ }
+ } finally {
+ unload();
+ }
+ }
+
+ /**
+ * Registers the callback methods that will be called when the JSONP response
+ * comes back. 2 callbacks are created, one to return the value, and one to
+ * notify a failure.
+ *
+ * @param callbacks the global JS object which stores callbacks
+ */
+ private native void registerCallbacks(JavaScriptObject callbacks) /*-{
+ var self = this;
+ var callback = new Object();
+ callbacks[this.@com.google.gwt.jsonp.client.JsonpRequest::callbackId] = callback;
+ callback.onSuccess = function(data) {
+ // Box primitive types
+ if (typeof data == 'boolean') {
+ data = @java.lang.Boolean::new(Z)(data);
+ } else if (typeof data == 'number') {
+ if (self.@com.google.gwt.jsonp.client.JsonpRequest::expectInteger) {
+ data = @java.lang.Integer::new(I)(data);
+ } else {
+ data = @java.lang.Double::new(D)(data);
+ }
+ }
+ self.@com.google.gwt.jsonp.client.JsonpRequest::onSuccess(Ljava/lang/Object;)(data);
+ };
+ if (this.@com.google.gwt.jsonp.client.JsonpRequest::failureCallbackParam) {
+ callback.onFailure = function(message) {
+ self.@com.google.gwt.jsonp.client.JsonpRequest::onFailure(Ljava/lang/String;)(message);
+ };
+ }
+ }-*/;
+
+ /**
+ * Cleans everything once the response has been received: deletes the script
+ * tag and unregisters the callback.
+ */
+ private void unload() {
+ /*
+ * Some browsers (IE7) require the script tag to be deleted outside the
+ * scope of the script itself. Therefore, we need to defer the delete
+ * statement after the callback execution.
+ */
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ unregisterCallbacks(CALLBACKS);
+ Node script = Document.get().getElementById(callbackId);
+ getDocumentElement().getFirstChild().removeChild(script);
+ }
+ });
+ }
+
+ private native void unregisterCallbacks(JavaScriptObject callbacks) /*-{
+ delete $wnd[this.@com.google.gwt.jsonp.client.JsonpRequest::callbackId];
+ }-*/;
+}
diff --git a/user/src/com/google/gwt/jsonp/client/JsonpRequestBuilder.java b/user/src/com/google/gwt/jsonp/client/JsonpRequestBuilder.java
new file mode 100644
index 0000000..7d26448
--- /dev/null
+++ b/user/src/com/google/gwt/jsonp/client/JsonpRequestBuilder.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2009 Google 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.google.gwt.jsonp.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+
+/**
+ * Class to send cross domain requests to an http server. The server will receive a request
+ * including a callback url parameter, which should be used to return the response as following:
+ *
+ * <pre><callback>(<json>);</pre>
+ *
+ * where <callback> is the url parameter (see {@link #setCallbackParam(String)}), and
+ * <json> is the response to the request in json format.
+ *
+ * This will result on the client to call the corresponding {@link AsyncCallback#onSuccess(Object)}
+ * method.
+ *
+ * <p>
+ * If needed, errors can be handled by a separate callback:
+ *
+ * <pre><failureCallback>(<error>);</pre>
+ *
+ * where <error> is a string containing an error message. This will result on the client to
+ * call the corresponding {@link AsyncCallback#onFailure(Throwable)} method. See
+ * {@link #setFailureCallbackParam(String)}.
+ *
+ * <p>
+ * Example using <a href="http://code.google.com/apis/gdata/json.html#Request">JSON Google Calendar
+ * GData API</a>:
+ *
+ * <pre>
+ * String url = "http://www.google.com/calendar/feeds/developer-calendar@google.com/public/full" +
+ * "?alt=json-in-script";
+ * JsonpRequestBuilder jsonp = new JsonpRequestBuilder();
+ * jsonp.requestObject(url,
+ * new AsyncCallback<Feed>() {
+ * public void onFailure(Throwable throwable) {
+ * Log.severe("Error: " + throwable);
+ * }
+ *
+ * public void onSuccess(Feed feed) {
+ * JsArray<Entry> entries = feed.getEntries();
+ * for (int i = 0; i < entries.length(); i++) {
+ * Entry entry = entries.get(i);
+ * Log.info(entry.getTitle() +
+ * " (" + entry.getWhere() + "): " +
+ * entry.getStartTime() + " -> " +
+ * entry.getEndTime());
+ * }
+ * }
+ * });
+ * </pre>
+ *
+ * This example uses these overlay types:
+ *
+ * <pre>
+ * class Entry extends JavaScriptObject {
+ * protected Entry() {}
+ *
+ * public final native String getTitle() /*-{
+ * return this.title.$t;
+ * }-*/;
+ *
+ * public final native String getWhere() /*-{
+ * return this.gd$where[0].valueString;
+ * }-*/;
+ *
+ * public final native String getStartTime() /*-{
+ * return this.gd$when ? this.gd$when[0].startTime : null;
+ * }-*/;
+ *
+ * public final native String getEndTime() /*-{
+ * return this.gd$when ? this.gd$when[0].endTime : null;
+ * }-*/;
+ * }
+ *
+ * class Feed extends JavaScriptObject {
+ * protected Feed() {}
+ *
+ * public final native JsArray<Entry> getEntries() /*-{
+ * return this.feed.entry;
+ * }-*/;
+ * }
+ * </pre>
+ *
+ * </p>
+ */
+public class JsonpRequestBuilder {
+ private int timeout = 10000;
+ private String callbackParam = "callback";
+ private String failureCallbackParam = null;
+
+ /**
+ * @return the name of the callback url parameter to send to the server. The default value is
+ * "callback".
+ */
+ public String getCallbackParam() {
+ return callbackParam;
+ }
+
+ /**
+ * @return the name of the failure callback url parameter to send to the server. The default is
+ * null.
+ */
+ public String getFailureCallbackParam() {
+ return failureCallbackParam;
+ }
+
+ /**
+ * @return the expected timeout (ms) for this request.
+ */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ public JsonpRequest<Boolean> requestBoolean(String url, AsyncCallback<Boolean> callback) {
+ return send(url, callback, false);
+ }
+
+ public JsonpRequest<Double> requestDouble(String url, AsyncCallback<Double> callback) {
+ return send(url, callback, false);
+ }
+
+ public JsonpRequest<Integer> requestInteger(String url, AsyncCallback<Integer> callback) {
+ return send(url, callback, true);
+ }
+
+ /**
+ * Sends a JSONP request and expects a JavaScript object as a result. The caller can either use
+ * {@link com.google.gwt.json.client.JSONObject} to parse it, or use a JavaScript overlay class.
+ */
+ public <T extends JavaScriptObject> JsonpRequest<T> requestObject(String url,
+ AsyncCallback<T> callback) {
+ return send(url, callback, false);
+ }
+
+ public JsonpRequest<String> requestString(String url, AsyncCallback<String> callback) {
+ return send(url, callback, false);
+ }
+
+ /**
+ * Sends a JSONP request and does not expect any results.
+ */
+ public void send(String url) {
+ send(url, null, false);
+ }
+
+ /**
+ * Sends a JSONP request, does not expect any result, but still allows to be notified when the
+ * request has been executed on the server.
+ */
+ public JsonpRequest<Void> send(String url, AsyncCallback<Void> callback) {
+ return send(url, callback, false);
+ }
+
+ /**
+ * @param callbackParam The name of the callback url parameter to send to the server. The default
+ * value is "callback".
+ */
+ public void setCallbackParam(String callbackParam) {
+ this.callbackParam = callbackParam;
+ }
+
+ /**
+ * @param failureCallbackParam The name of the failure callback url parameter to send to the
+ * server. The default is null.
+ */
+ public void setFailureCallbackParam(String failureCallbackParam) {
+ this.failureCallbackParam = failureCallbackParam;
+ }
+
+ /**
+ * @param timeout The expected timeout (ms) for this request. The default is 10s.
+ */
+ public void setTimeout(int timeout) {
+ this.timeout = timeout;
+ }
+
+ private <T> JsonpRequest<T> send(String url, AsyncCallback<T> callback, boolean expectInteger) {
+ JsonpRequest<T> request = new JsonpRequest<T>(callback, timeout, expectInteger, callbackParam,
+ failureCallbackParam);
+ request.send(url);
+ return request;
+ }
+}
diff --git a/user/src/com/google/gwt/jsonp/client/TimeoutException.java b/user/src/com/google/gwt/jsonp/client/TimeoutException.java
new file mode 100644
index 0000000..703f663
--- /dev/null
+++ b/user/src/com/google/gwt/jsonp/client/TimeoutException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2009 Google 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.google.gwt.jsonp.client;
+
+/**
+ * Exception sent when a JSONP calls expires the timeout.
+ */
+public class TimeoutException extends Exception {
+
+ public TimeoutException() {
+ }
+
+ public TimeoutException(String s) {
+ super(s);
+ }
+}
diff --git a/user/test/com/google/gwt/jsonp/JsonpRequestSuite.java b/user/test/com/google/gwt/jsonp/JsonpRequestSuite.java
new file mode 100644
index 0000000..34fe230
--- /dev/null
+++ b/user/test/com/google/gwt/jsonp/JsonpRequestSuite.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2009 Google 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.google.gwt.jsonp;
+
+import com.google.gwt.jsonp.client.JsonpRequestTest;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Suite for JSONP tests.
+ */
+public class JsonpRequestSuite extends TestCase {
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.addTestSuite(JsonpRequestTest.class);
+ return suite;
+ }
+}
diff --git a/user/test/com/google/gwt/jsonp/JsonpTest.gwt.xml b/user/test/com/google/gwt/jsonp/JsonpTest.gwt.xml
new file mode 100644
index 0000000..989fc8d
--- /dev/null
+++ b/user/test/com/google/gwt/jsonp/JsonpTest.gwt.xml
@@ -0,0 +1,25 @@
+<!-- -->
+<!-- Copyright 2009 Google 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 -->
+<!-- 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. License for the specific language governing permissions and -->
+<!-- limitations under the License. -->
+
+<module>
+ <!-- Inherit the JUnit support -->
+ <inherits name='com.google.gwt.junit.JUnit'/>
+ <inherits name = 'com.google.gwt.jsonp.Jsonp'/>
+
+ <!-- Include client-side source for the test cases -->
+ <source path="client"/>
+
+ <!-- JSONP servlet for testing -->
+ <servlet path='/echo' class='com.google.gwt.jsonp.server.EchoServlet'/>
+</module>
diff --git a/user/test/com/google/gwt/jsonp/client/JsonpRequestTest.java b/user/test/com/google/gwt/jsonp/client/JsonpRequestTest.java
new file mode 100644
index 0000000..7784827
--- /dev/null
+++ b/user/test/com/google/gwt/jsonp/client/JsonpRequestTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2009 Google 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.google.gwt.jsonp.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * Tests for {@link JsonpRequest}.
+ */
+public class JsonpRequestTest extends GWTTestCase {
+
+ /**
+ * Checks that an error is received.
+ */
+ private class AssertFailureCallback<T> implements AsyncCallback<T> {
+ private String expectedMessage;
+
+ public AssertFailureCallback(String expectedMessage) {
+ this.expectedMessage = expectedMessage;
+ }
+
+ public void onFailure(Throwable throwable) {
+ assertEquals(expectedMessage, throwable.getMessage());
+ finishTest();
+ }
+
+ public void onSuccess(T value) {
+ fail();
+ }
+ }
+
+ /**
+ * Checks that the received value is as expected.
+ */
+ private class AssertSuccessCallback<T> implements AsyncCallback<T> {
+ private T expectedValue;
+
+ private AssertSuccessCallback(T expectedValue) {
+ this.expectedValue = expectedValue;
+ }
+
+ public void onFailure(Throwable throwable) {
+ fail();
+ }
+
+ public void onSuccess(T value) {
+ assertEquals(expectedValue, value);
+ finishTest();
+ }
+ }
+
+ /**
+ * Checks that a timeout happens.
+ */
+ private class AssertTimeoutException<T> implements AsyncCallback<T> {
+ public void onFailure(Throwable throwable) {
+ assertTrue(throwable instanceof TimeoutException);
+ finishTest();
+ }
+
+ public void onSuccess(T value) {
+ fail();
+ }
+ }
+
+ private static String echo(String value) {
+ return GWT.getModuleBaseURL() + "echo?action=SUCCESS&value=" + value;
+ }
+
+ private static String echoFailure(String error) {
+ return GWT.getModuleBaseURL() + "echo?action=FAILURE&error=" + error;
+ }
+
+ private static String echoTimeout() {
+ return GWT.getModuleBaseURL() + "echo?action=TIMEOUT";
+ }
+
+ private JsonpRequestBuilder jsonp;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.jsonp.JsonpTest";
+ }
+
+ public void testBooleanFalse() {
+ jsonp.requestBoolean(echo("false"), new AssertSuccessCallback<Boolean>(
+ Boolean.FALSE));
+ delayTestFinish(500);
+ }
+
+ public void testBooleanTrue() {
+ jsonp.requestBoolean(echo("true"), new AssertSuccessCallback<Boolean>(
+ Boolean.TRUE));
+ delayTestFinish(500);
+ }
+
+ public void testDouble() {
+ jsonp.requestDouble(echo("123.456"), new AssertSuccessCallback<Double>(
+ 123.456));
+ delayTestFinish(500);
+ }
+
+ public void testFailureCallback() {
+ jsonp.setFailureCallbackParam("failureCallback");
+ jsonp.requestString(echoFailure("ERROR"),
+ new AssertFailureCallback<String>("ERROR"));
+ delayTestFinish(500);
+ }
+
+ public void testInteger() {
+ jsonp.requestInteger(echo("123"), new AssertSuccessCallback<Integer>(123));
+ delayTestFinish(500);
+ }
+
+ /**
+ * Tests that if no failure callback is defined, the servlet receives well
+ * only a 'callback' parameter, and sends back the error to it.
+ */
+ public void testNoFailureCallback() {
+ jsonp.setFailureCallbackParam(null);
+ jsonp.requestString(echoFailure("ERROR"),
+ new AssertSuccessCallback<String>("ERROR"));
+ delayTestFinish(500);
+ }
+
+ public void testNullBoolean() {
+ jsonp.requestBoolean(echo("null"), new AssertSuccessCallback<Boolean>(null));
+ delayTestFinish(500);
+ }
+
+ public void testNullDouble() {
+ jsonp.requestDouble(echo("null"), new AssertSuccessCallback<Double>(null));
+ delayTestFinish(500);
+ }
+
+ public void testNullInteger() {
+ jsonp.requestInteger(echo("null"), new AssertSuccessCallback<Integer>(null));
+ delayTestFinish(500);
+ }
+
+ public void testNullString() {
+ jsonp.requestString(echo("null"), new AssertSuccessCallback<String>(null));
+ delayTestFinish(500);
+ }
+
+ public void testString() {
+ jsonp.requestString(echo("'Hello'"), new AssertSuccessCallback<String>(
+ "Hello"));
+ delayTestFinish(500);
+ }
+
+ public void testTimeout() {
+ jsonp.requestString(echoTimeout(), new AssertTimeoutException<String>());
+ delayTestFinish(2000);
+ }
+
+ public void testVoid() {
+ jsonp.send(echo(null), new AssertSuccessCallback<Void>(null));
+ delayTestFinish(500);
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ jsonp = new JsonpRequestBuilder();
+ jsonp.setTimeout(1000);
+ }
+}
diff --git a/user/test/com/google/gwt/jsonp/server/EchoServlet.java b/user/test/com/google/gwt/jsonp/server/EchoServlet.java
new file mode 100644
index 0000000..bdee3ef
--- /dev/null
+++ b/user/test/com/google/gwt/jsonp/server/EchoServlet.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2009 Google 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.google.gwt.jsonp.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A servlet that returns a given value, following the JSONP protocol.
+ * Expected url parameters:
+ *
+ * <ul>
+ * <li>action: one of the following values:
+ * <ul>
+ * <li>TIMEOUT: don't respond anything to simulate a timeout
+ * <li>SUCCESS: return a JSON value
+ * <li>FAILURE: return an error
+ * </ul>
+ * <li>value: the JSON value to return if action == "SUCCESS"
+ * <li>error: the error message to return if action == "FAILURE"
+ * </ul>
+ */
+public class EchoServlet extends HttpServlet {
+
+ private enum Action {
+ SUCCESS,
+ FAILURE,
+ TIMEOUT
+ }
+
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse res)
+ throws ServletException, IOException {
+ switch (Action.valueOf(req.getParameter("action"))) {
+ case SUCCESS: {
+ String callback = req.getParameter("callback");
+ String value = req.getParameter("value");
+ if (value == null) {
+ value = "";
+ }
+ res.getWriter().println(callback + "(" + value + ");");
+ break;
+ }
+
+ case FAILURE: {
+ String failureCallback = req.getParameter("failureCallback");
+ String error = req.getParameter("error");
+ if (failureCallback != null) {
+ res.getWriter().println(failureCallback + "('" + error + "');");
+ } else {
+ // If no failure callback is defined, send the error through the
+ // success callback.
+ String callback = req.getParameter("callback");
+ res.getWriter().println(callback + "('" + error + "');");
+ }
+ break;
+ }
+
+ case TIMEOUT:
+ // Don't respond anything so that a timeout happens.
+ }
+ }
+}