diff options
author | Gert Scholten <gscholt@gmail.com> | 2009-11-25 11:22:01 +0100 |
---|---|---|
committer | Gert Scholten <gscholt@gmail.com> | 2009-11-25 11:22:01 +0100 |
commit | 96d025bfaa31daeb2192ba41d9f7ce759201f864 (patch) | |
tree | 068b2d7add26f75a868b9b89a3063bbddb2e4d31 | |
parent | 18edfa13362d089fb6c80f501ae3b2c7b94df323 (diff) | |
download | gwtjsonrpc-96d025bfaa31daeb2192ba41d9f7ce759201f864.tar.gz |
Add support for JSON-RPC version 2.0 over HTTP POST and GET for the client.
Change-Id: I0047ea8bb18cb5840f56824d08480d37fca77e22
Signed-off-by: Gert Scholten <gscholt@gmail.com>
10 files changed, 607 insertions, 150 deletions
diff --git a/src/main/java/com/google/gwtjsonrpc/client/AbstractJsonProxy.java b/src/main/java/com/google/gwtjsonrpc/client/AbstractJsonProxy.java index 5e80a62..f92216a 100644 --- a/src/main/java/com/google/gwtjsonrpc/client/AbstractJsonProxy.java +++ b/src/main/java/com/google/gwtjsonrpc/client/AbstractJsonProxy.java @@ -55,9 +55,13 @@ public abstract class AbstractJsonProxy implements JsonDefTarget { if (url == null) { throw new NoServiceEntryPointSpecifiedException(); } - new JsonCall<T>(this, methodName, reqData, ser, cb).send(); + newJsonCall(this, methodName, reqData, ser, cb).send(); } + protected abstract <T> JsonCall<T> newJsonCall(AbstractJsonProxy proxy, + final String methodName, final String reqData, + final ResultDeserializer<T> ser, final AsyncCallback<T> cb); + protected static native JavaScriptObject hostPageCacheGetOnce(String name) /*-{ var r = $wnd[name];$wnd[name] = null;return r ? {result: r} : null; }-*/; diff --git a/src/main/java/com/google/gwtjsonrpc/client/JsonCall.java b/src/main/java/com/google/gwtjsonrpc/client/JsonCall.java index 4927fa5..fb4feed 100644 --- a/src/main/java/com/google/gwtjsonrpc/client/JsonCall.java +++ b/src/main/java/com/google/gwtjsonrpc/client/JsonCall.java @@ -20,14 +20,10 @@ import com.google.gwt.http.client.Request; import com.google.gwt.http.client.RequestBuilder; import com.google.gwt.http.client.RequestCallback; import com.google.gwt.http.client.RequestException; -import com.google.gwt.http.client.Response; -import com.google.gwt.json.client.JSONObject; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.rpc.InvocationException; -import com.google.gwt.user.client.rpc.StatusCodeException; -class JsonCall<T> implements RequestCallback { - private static final JavaScriptObject jsonParser; +abstract class JsonCall<T> implements RequestCallback { + protected static final JavaScriptObject jsonParser; static { jsonParser = selectJsonParser(); @@ -61,12 +57,12 @@ class JsonCall<T> implements RequestCallback { return function(expr) { return eval('(' + expr + ')'); }; }-*/; - private final AbstractJsonProxy proxy; - private final String methodName; - private final String requestParams; - private final ResultDeserializer<T> resultDeserializer; - private final AsyncCallback<T> callback; - private int attempts; + protected final AbstractJsonProxy proxy; + protected final String methodName; + protected final String requestParams; + protected final ResultDeserializer<T> resultDeserializer; + protected final AsyncCallback<T> callback; + protected int attempts; JsonCall(final AbstractJsonProxy abstractJsonProxy, final String methodName, final String requestParams, @@ -87,27 +83,9 @@ class JsonCall<T> implements RequestCallback { return methodName; } - void send() { - final StringBuilder body = new StringBuilder(); - body.append("{\"version\":\"1.1\",\"method\":\""); - body.append(methodName); - body.append("\",\"params\":["); - body.append(requestParams); - body.append("]"); - final String xsrfKey = proxy.getXsrfManager().getToken(proxy); - if (xsrfKey != null) { - body.append(",\"xsrfKey\":"); - body.append(JsonSerializer.escapeString(xsrfKey)); - } - body.append("}"); - - final RequestBuilder rb; - rb = new RequestBuilder(RequestBuilder.POST, proxy.url); - rb.setHeader("Content-Type", JsonUtil.JSON_REQ_CT); - rb.setHeader("Accept", JsonUtil.JSON_TYPE); - rb.setCallback(this); - rb.setRequestData(body.toString()); + abstract void send(); + protected void send(RequestBuilder rb) { try { attempts++; rb.send(); @@ -121,59 +99,6 @@ class JsonCall<T> implements RequestCallback { } } - public void onResponseReceived(final Request req, final Response rsp) { - final int sc = rsp.getStatusCode(); - if (isJsonBody(rsp)) { - final RpcResult r; - try { - r = parse(jsonParser, rsp.getText()); - } catch (RuntimeException e) { - fireEvent(RpcCompleteEvent.e); - callback.onFailure(new InvocationException("Bad JSON response: " + e)); - return; - } - - if (r.xsrfKey() != null) { - proxy.getXsrfManager().setToken(proxy, r.xsrfKey()); - } - - if (r.error() != null) { - final String errmsg = r.error().message(); - if (JsonUtil.ERROR_INVALID_XSRF.equals(errmsg)) { - if (attempts < 2) { - // The XSRF cookie was invalidated (or didn't exist) and the - // service demands we have one in place to make calls to it. - // A new token was returned to us, so start the request over. - // - send(); - } else { - fireEvent(RpcCompleteEvent.e); - callback.onFailure(new InvocationException(errmsg)); - } - } else { - fireEvent(RpcCompleteEvent.e); - callback.onFailure(new RemoteJsonException(errmsg, r.error().code(), - new JSONObject(r.error()).get("error"))); - } - return; - } - - if (sc == Response.SC_OK) { - fireEvent(RpcCompleteEvent.e); - JsonUtil.invoke(resultDeserializer, callback, r); - return; - } - } - - if (sc == Response.SC_OK) { - fireEvent(RpcCompleteEvent.e); - callback.onFailure(new InvocationException("No JSON response")); - } else { - fireEvent(RpcCompleteEvent.e); - callback.onFailure(new StatusCodeException(sc, rsp.getStatusText())); - } - } - public void onError(final Request request, final Throwable exception) { fireEvent(RpcCompleteEvent.e); if (exception.getClass() == RuntimeException.class @@ -188,53 +113,8 @@ class JsonCall<T> implements RequestCallback { } } - private <S extends EventHandler> void fireEvent(BaseRpcEvent<S> e) { + protected <S extends EventHandler> void fireEvent(BaseRpcEvent<S> e) { e.call = this; JsonUtil.fireEvent(e); } - - private static boolean isJsonBody(final Response rsp) { - String type = rsp.getHeader("Content-Type"); - if (type == null) { - return false; - } - int semi = type.indexOf(';'); - if (semi >= 0) { - type = type.substring(0, semi).trim(); - } - return JsonUtil.JSON_TYPE.equals(type); - } - - /** - * Call a JSON parser javascript function to parse an encoded JSON string. - * - * @param parser a javascript function - * @param json encoded JSON text - * @return the parsed data - * @see #jsonParser - */ - private static final native RpcResult parse(JavaScriptObject parserFunction, - String json) - /*-{ - return parserFunction(json); - }-*/; - - - private static class RpcResult extends JavaScriptObject { - protected RpcResult() { - } - - final native RpcError error()/*-{ return this.error; }-*/; - - final native String xsrfKey()/*-{ return this.xsrfKey; }-*/; - } - - private static class RpcError extends JavaScriptObject { - protected RpcError() { - } - - final native String message()/*-{ return this.message; }-*/; - - final native int code()/*-{ return this.code; }-*/; - } } diff --git a/src/main/java/com/google/gwtjsonrpc/client/JsonCall11HttpPost.java b/src/main/java/com/google/gwtjsonrpc/client/JsonCall11HttpPost.java new file mode 100644 index 0000000..126d007 --- /dev/null +++ b/src/main/java/com/google/gwtjsonrpc/client/JsonCall11HttpPost.java @@ -0,0 +1,158 @@ +// 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.gwtjsonrpc.client; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.http.client.Request; +import com.google.gwt.http.client.RequestBuilder; +import com.google.gwt.http.client.Response; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.rpc.InvocationException; +import com.google.gwt.user.client.rpc.StatusCodeException; + +/** JsonCall implementation for JsonRPC version 1.1 over HTTP POST */ +public class JsonCall11HttpPost<T> extends JsonCall<T> { + + public JsonCall11HttpPost(AbstractJsonProxy abstractJsonProxy, + String methodName, String requestParams, + ResultDeserializer<T> resultDeserializer, AsyncCallback<T> callback) { + super(abstractJsonProxy, methodName, requestParams, resultDeserializer, + callback); + } + + @Override + void send() { + final StringBuilder body = new StringBuilder(); + body.append("{\"version\":\"1.1\",\"method\":\""); + body.append(methodName); + body.append("\",\"params\":"); + body.append(requestParams); + final String xsrfKey = proxy.getXsrfManager().getToken(proxy); + if (xsrfKey != null) { + body.append(",\"xsrfKey\":"); + body.append(JsonSerializer.escapeString(xsrfKey)); + } + body.append("}"); + + final RequestBuilder rb; + rb = new RequestBuilder(RequestBuilder.POST, proxy.url); + rb.setHeader("Content-Type", JsonUtil.JSON_REQ_CT); + rb.setHeader("Accept", JsonUtil.JSON_TYPE); + rb.setCallback(this); + rb.setRequestData(body.toString()); + + send(rb); + } + + @Override + public void onResponseReceived(final Request req, final Response rsp) { + final int sc = rsp.getStatusCode(); + if (isJsonBody(rsp)) { + final RpcResult r; + try { + r = parse(jsonParser, rsp.getText()); + } catch (RuntimeException e) { + fireEvent(RpcCompleteEvent.e); + callback.onFailure(new InvocationException("Bad JSON response: " + e)); + return; + } + + if (r.xsrfKey() != null) { + proxy.getXsrfManager().setToken(proxy, r.xsrfKey()); + } + + if (r.error() != null) { + final String errmsg = r.error().message(); + if (JsonUtil.ERROR_INVALID_XSRF.equals(errmsg)) { + if (attempts < 2) { + // The XSRF cookie was invalidated (or didn't exist) and the + // service demands we have one in place to make calls to it. + // A new token was returned to us, so start the request over. + // + send(); + } else { + fireEvent(RpcCompleteEvent.e); + callback.onFailure(new InvocationException(errmsg)); + } + } else { + fireEvent(RpcCompleteEvent.e); + callback.onFailure(new RemoteJsonException(errmsg, r.error().code(), + new JSONObject(r.error()).get("error"))); + } + return; + } + + if (sc == Response.SC_OK) { + fireEvent(RpcCompleteEvent.e); + JsonUtil.invoke(resultDeserializer, callback, r); + return; + } + } + + if (sc == Response.SC_OK) { + fireEvent(RpcCompleteEvent.e); + callback.onFailure(new InvocationException("No JSON response")); + } else { + fireEvent(RpcCompleteEvent.e); + callback.onFailure(new StatusCodeException(sc, rsp.getStatusText())); + } + } + + private static boolean isJsonBody(final Response rsp) { + String type = rsp.getHeader("Content-Type"); + if (type == null) { + return false; + } + int semi = type.indexOf(';'); + if (semi >= 0) { + type = type.substring(0, semi).trim(); + } + return JsonUtil.JSON_TYPE.equals(type); + } + + /** + * Call a JSON parser javascript function to parse an encoded JSON string. + * + * @param parser a javascript function + * @param json encoded JSON text + * @return the parsed data + * @see #jsonParser + */ + private static final native RpcResult parse(JavaScriptObject parserFunction, + String json) + /*-{ + return parserFunction(json); + }-*/; + + + private static class RpcResult extends JavaScriptObject { + protected RpcResult() { + } + + final native RpcError error()/*-{ return this.error; }-*/; + + final native String xsrfKey()/*-{ return this.xsrfKey; }-*/; + } + + private static class RpcError extends JavaScriptObject { + protected RpcError() { + } + + final native String message()/*-{ return this.message; }-*/; + + final native int code()/*-{ return this.code; }-*/; + } +} diff --git a/src/main/java/com/google/gwtjsonrpc/client/JsonCall20.java b/src/main/java/com/google/gwtjsonrpc/client/JsonCall20.java new file mode 100644 index 0000000..27028de --- /dev/null +++ b/src/main/java/com/google/gwtjsonrpc/client/JsonCall20.java @@ -0,0 +1,137 @@ +// Copyright 2009 Gert Scholten +// +// 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.gwtjsonrpc.client; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.http.client.Request; +import com.google.gwt.http.client.Response; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.rpc.InvocationException; +import com.google.gwt.user.client.rpc.StatusCodeException; + +/** Base JsonCall implementation for JsonRPC version 2.0 */ +abstract class JsonCall20<T> extends JsonCall<T> { + protected static int lastRequestId = 0; + + protected int requestId; + + JsonCall20(AbstractJsonProxy abstractJsonProxy, String methodName, + String requestParams, ResultDeserializer<T> resultDeserializer, + AsyncCallback<T> callback) { + super(abstractJsonProxy, methodName, requestParams, resultDeserializer, + callback); + } + + @Override + public void onResponseReceived(final Request req, final Response rsp) { + final int sc = rsp.getStatusCode(); + if (isJsonBody(rsp)) { + final RpcResult r; + try { + r = parse(jsonParser, rsp.getText()); + } catch (RuntimeException e) { + fireEvent(RpcCompleteEvent.e); + callback.onFailure(new InvocationException("Bad JSON response: " + e)); + return; + } + + if (r.xsrfKey() != null) { + proxy.getXsrfManager().setToken(proxy, r.xsrfKey()); + } + + if (r.error() != null) { + // TODO: define status code for the invalid XSRF msg for 2.0 (-32099 ?) + final String errmsg = r.error().message(); + if (JsonUtil.ERROR_INVALID_XSRF.equals(errmsg)) { + if (attempts < 2) { + // The XSRF cookie was invalidated (or didn't exist) and the + // service demands we have one in place to make calls to it. + // A new token was returned to us, so start the request over. + // + send(); + } else { + fireEvent(RpcCompleteEvent.e); + callback.onFailure(new InvocationException(errmsg)); + } + } else { + fireEvent(RpcCompleteEvent.e); + callback.onFailure(new RemoteJsonException(errmsg, r.error().code(), + new JSONObject(r.error()).get("data"))); + } + return; + } + + if (sc == Response.SC_OK) { + fireEvent(RpcCompleteEvent.e); + JsonUtil.invoke(resultDeserializer, callback, r); + return; + } + } + + if (sc == Response.SC_OK) { + fireEvent(RpcCompleteEvent.e); + callback.onFailure(new InvocationException("No JSON response")); + } else { + fireEvent(RpcCompleteEvent.e); + callback.onFailure(new StatusCodeException(sc, rsp.getStatusText())); + } + } + + protected static boolean isJsonBody(final Response rsp) { + String type = rsp.getHeader("Content-Type"); + if (type == null) { + return false; + } + int semi = type.indexOf(';'); + if (semi >= 0) { + type = type.substring(0, semi).trim(); + } + return JsonUtil.JSONRPC20_ACCEPT_CTS.contains(type); + } + + /** + * Call a JSON parser javascript function to parse an encoded JSON string. + * + * @param parser a javascript function + * @param json encoded JSON text + * @return the parsed data + * @see #jsonParser + */ + private static final native RpcResult parse(JavaScriptObject parserFunction, + String json) + /*-{ + return parserFunction(json); + }-*/; + + + private static class RpcResult extends JavaScriptObject { + protected RpcResult() { + } + + final native RpcError error()/*-{ return this.error; }-*/; + + final native String xsrfKey()/*-{ return this.xsrfKey; }-*/; + } + + private static class RpcError extends JavaScriptObject { + protected RpcError() { + } + + final native String message()/*-{ return this.message; }-*/; + + final native int code()/*-{ return this.code; }-*/; + } +} diff --git a/src/main/java/com/google/gwtjsonrpc/client/JsonCall20HttpGet.java b/src/main/java/com/google/gwtjsonrpc/client/JsonCall20HttpGet.java new file mode 100644 index 0000000..416ff3c --- /dev/null +++ b/src/main/java/com/google/gwtjsonrpc/client/JsonCall20HttpGet.java @@ -0,0 +1,74 @@ +// Copyright 2009 Gert Scholten +// +// 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.gwtjsonrpc.client; + +import com.google.gwt.http.client.RequestBuilder; +import com.google.gwt.http.client.URL; +import com.google.gwt.user.client.rpc.AsyncCallback; + +/** JsonCall implementation for JsonRPC version 2.0 over HTTP POST */ +public class JsonCall20HttpGet<T> extends JsonCall20<T> { + private String encodedRequestParams; + + public JsonCall20HttpGet(AbstractJsonProxy abstractJsonProxy, + String methodName, String requestParams, + ResultDeserializer<T> resultDeserializer, AsyncCallback<T> callback) { + super(abstractJsonProxy, methodName, requestParams, resultDeserializer, + callback); + encodedRequestParams = URL.encodeComponent(encodeBase64(requestParams)); + } + + @Override + void send() { + requestId = ++lastRequestId; + final StringBuilder url = new StringBuilder(proxy.url); + url.append("?jsonrpc=2.0&method=").append(methodName); + url.append("¶ms=").append(encodedRequestParams); + url.append("&id=").append(requestId); + + final RequestBuilder rb; + rb = new RequestBuilder(RequestBuilder.GET, url.toString()); + rb.setHeader("Content-Type", JsonUtil.JSONRPC20_REQ_CT); + rb.setHeader("Accept", JsonUtil.JSONRPC20_ACCEPT_CTS); + rb.setCallback(this); + + send(rb); + } + + /** + * Javascript base64 encoding implementation from. + * + * @see http://ecmanaut.googlecode.com/svn/trunk/lib/base64.js + */ + private static native String encodeBase64(String data) + /*-{ + var out = "", c1, c2, c3, e1, e2, e3, e4; + for (var i = 0; i < data.length; ) { + c1 = data.charCodeAt(i++); + c2 = data.charCodeAt(i++); + c3 = data.charCodeAt(i++); + e1 = c1 >> 2; + e2 = ((c1 & 3) << 4) + (c2 >> 4); + e3 = ((c2 & 15) << 2) + (c3 >> 6); + e4 = c3 & 63; + if (isNaN(c2)) + e3 = e4 = 64; + else if (isNaN(c3)) + e4 = 64; + out += tab.charAt(e1) + tab.charAt(e2) + tab.charAt(e3) + tab.charAt(e4); + } + return out; + }-*/; +} diff --git a/src/main/java/com/google/gwtjsonrpc/client/JsonCall20HttpPost.java b/src/main/java/com/google/gwtjsonrpc/client/JsonCall20HttpPost.java new file mode 100644 index 0000000..a875d24 --- /dev/null +++ b/src/main/java/com/google/gwtjsonrpc/client/JsonCall20HttpPost.java @@ -0,0 +1,54 @@ +// Copyright 2009 Gert Scholten +// +// 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.gwtjsonrpc.client; + +import com.google.gwt.http.client.RequestBuilder; +import com.google.gwt.user.client.rpc.AsyncCallback; + +/** JsonCall implementation for JsonRPC version 2.0 over HTTP POST */ +public class JsonCall20HttpPost<T> extends JsonCall20<T> { + public JsonCall20HttpPost(AbstractJsonProxy abstractJsonProxy, + String methodName, String requestParams, + ResultDeserializer<T> resultDeserializer, AsyncCallback<T> callback) { + super(abstractJsonProxy, methodName, requestParams, resultDeserializer, + callback); + } + + @Override + void send() { + requestId = ++lastRequestId; + final StringBuilder body = new StringBuilder(); + body.append("{\"jsonrpc\":\"2.0\",\"method\":\""); + body.append(methodName); + body.append("\",\"params\":["); + body.append(requestParams); + body.append("],\"id\":").append(requestId); + final String xsrfKey = proxy.getXsrfManager().getToken(proxy); + if (xsrfKey != null) { + body.append(",\"xsrfKey\":"); + body.append(JsonSerializer.escapeString(xsrfKey)); + } + body.append("}"); + + final RequestBuilder rb; + rb = new RequestBuilder(RequestBuilder.POST, proxy.url); + rb.setHeader("Content-Type", JsonUtil.JSONRPC20_REQ_CT); + rb.setHeader("Accept", JsonUtil.JSONRPC20_ACCEPT_CTS); + rb.setCallback(this); + rb.setRequestData(body.toString()); + + send(rb); + } +} diff --git a/src/main/java/com/google/gwtjsonrpc/client/JsonUtil.java b/src/main/java/com/google/gwtjsonrpc/client/JsonUtil.java index 7f1f47a..2628cc4 100644 --- a/src/main/java/com/google/gwtjsonrpc/client/JsonUtil.java +++ b/src/main/java/com/google/gwtjsonrpc/client/JsonUtil.java @@ -33,6 +33,17 @@ public class JsonUtil { /** Request Content-Type header for JSON data. */ static final String JSON_REQ_CT = JSON_TYPE + "; charset=utf-8"; + /** Json-rpc 2.0: Proper Content-Type header value for JSON encoded data. */ + public static final String JSONRPC20_TYPE = "application/json-rpc"; + + /** Json-rpc 2.0: Request Content-Type header for JSON data. */ + static final String JSONRPC20_REQ_CT = JSON_TYPE + "; charset=utf-8"; + + /** Json-rpc 2.0: Content types that we SHOULD accept as being valid */ + static final String JSONRPC20_ACCEPT_CTS = + JSON_TYPE + ",application/json,application/jsonrequest"; + + /** Error message when xsrfKey in request is missing or invalid. */ public static final String ERROR_INVALID_XSRF = "Invalid xsrfKey in request"; diff --git a/src/main/java/com/google/gwtjsonrpc/client/RemoteJsonException.java b/src/main/java/com/google/gwtjsonrpc/client/RemoteJsonException.java index 4a7592f..a363e83 100644 --- a/src/main/java/com/google/gwtjsonrpc/client/RemoteJsonException.java +++ b/src/main/java/com/google/gwtjsonrpc/client/RemoteJsonException.java @@ -17,30 +17,38 @@ package com.google.gwtjsonrpc.client; import com.google.gwt.json.client.JSONValue; /** - * Exception given to {@link com.google.gwt.user.client.rpc.AsyncCallback#onFailure(Throwable)}. + * Exception given to + * {@link com.google.gwt.user.client.rpc.AsyncCallback#onFailure(Throwable)}. * <p> * This exception is used if the remote JSON server has returned a well-formed * JSON error response. */ public class RemoteJsonException extends Exception { + private static final long serialVersionUID = 1L; private int code; - private JSONValue error; + private JSONValue data; /** - * Construct a new exception representing a welformed JSON error response. - * + * Construct a new exception representing a well formed JSON error response. + * * @param message A String value that provides a short description of the - * error. - * @param code A number that indicates the actual error that occurred. - * @param error A JSON value instance that carries custom and - * application-specific error information. + * error + * @param code A number that indicates the actual error that occurred + * @param data A JSON value instance that carries custom and + * application-specific error information */ - public RemoteJsonException(final String message, int code, JSONValue error) { + public RemoteJsonException(final String message, int code, JSONValue data) { super(message); this.code = code; - this.error = error; + this.data = data; } + /** + * Creates a new RemoteJsonException with code 999 and no data. + * + * @param message A String value that provides a short description of the + * error + */ public RemoteJsonException(final String message) { this(message, 999, null); } @@ -49,7 +57,7 @@ public class RemoteJsonException extends Exception { * Gets the error code. * <p> * Note that the JSON-RPC 1.1 draf does not define error codes yet. - * + * * @return A number that indicates the actual error that occurred. */ public int getCode() { @@ -57,12 +65,21 @@ public class RemoteJsonException extends Exception { } /** - * Gets the extra error information. - * - * @return <code>null</code> if no error information was specified by the - * server, or value. + * Same as getData. + * + * @return the error data, or <code>null</code> if none was specified + * @see #getData */ public JSONValue getError() { - return error; + return data; + } + + /** + * Gets the extra error information supplied by the service. + * + * @return the error data, or <code>null</code> if none was specified + */ + public JSONValue getData() { + return data; } } diff --git a/src/main/java/com/google/gwtjsonrpc/client/RpcImpl.java b/src/main/java/com/google/gwtjsonrpc/client/RpcImpl.java new file mode 100644 index 0000000..9868e70 --- /dev/null +++ b/src/main/java/com/google/gwtjsonrpc/client/RpcImpl.java @@ -0,0 +1,67 @@ +// 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.gwtjsonrpc.client; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Specify the json rpc protocol version and transport mechanism to be used for + * a service. + * <p> + * Default is version 1.1 over HTTP POST. + * <p> + * <b>Note: if you use the generated (servlet), only version 1.1 over HTTP POST + * is supported</b>. + */ +@Target(ElementType.TYPE) +public @interface RpcImpl { + /** + * JSON-RPC protocol versions. + */ + public enum Version { + /** + * Version 1.1. + * + * @see <a + * href="http://groups.google.com/group/json-rpc/web/json-rpc-1-1-wd">Spec</a> + */ + V1_1, + /** + * Version 2.0. + * + * @see <a + * href="http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal">Spec</a> + */ + V2_0 + } + /** + * Supported transport mechanisms. + */ + public enum Transport { + HTTP_POST, HTTP_GET + } + + /** + * Specify the JSON-RPC version. Default is version 1.1. + */ + Version version() default Version.V1_1; + + /** + * Specify the transport protocol used to make the RPC call. Default is HTTP + * POST. + */ + Transport transport() default Transport.HTTP_POST; +} diff --git a/src/main/java/com/google/gwtjsonrpc/rebind/ProxyCreator.java b/src/main/java/com/google/gwtjsonrpc/rebind/ProxyCreator.java index 573254d..0bda41f 100644 --- a/src/main/java/com/google/gwtjsonrpc/rebind/ProxyCreator.java +++ b/src/main/java/com/google/gwtjsonrpc/rebind/ProxyCreator.java @@ -19,6 +19,7 @@ import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.TreeLogger.Type; import com.google.gwt.core.ext.typeinfo.JArrayType; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JMethod; @@ -37,9 +38,15 @@ import com.google.gwt.user.rebind.SourceWriter; import com.google.gwtjsonrpc.client.AbstractJsonProxy; import com.google.gwtjsonrpc.client.CallbackHandle; import com.google.gwtjsonrpc.client.HostPageCache; +import com.google.gwtjsonrpc.client.JsonCall11HttpPost; +import com.google.gwtjsonrpc.client.JsonCall20HttpGet; +import com.google.gwtjsonrpc.client.JsonCall20HttpPost; import com.google.gwtjsonrpc.client.JsonSerializer; import com.google.gwtjsonrpc.client.JsonUtil; import com.google.gwtjsonrpc.client.ResultDeserializer; +import com.google.gwtjsonrpc.client.RpcImpl; +import com.google.gwtjsonrpc.client.RpcImpl.Transport; +import com.google.gwtjsonrpc.client.RpcImpl.Version; import java.io.PrintWriter; import java.util.HashSet; @@ -60,7 +67,8 @@ class ProxyCreator { String create(final TreeLogger logger, final GeneratorContext context) throws UnableToCompleteException { serializerCreator = new SerializerCreator(context); - deserializerCreator = new ResultDeserializerCreator(context, serializerCreator); + deserializerCreator = + new ResultDeserializerCreator(context, serializerCreator); final TypeOracle typeOracle = context.getTypeOracle(); try { asyncCallbackClass = typeOracle.getType(AsyncCallback.class.getName()); @@ -76,6 +84,7 @@ class ProxyCreator { } generateProxyConstructor(logger, srcWriter); + generateProxyCallCreator(logger, srcWriter); generateProxyMethods(logger, srcWriter); srcWriter.commit(logger); @@ -199,6 +208,8 @@ class ProxyCreator { cf.addImport(AbstractJsonProxy.class.getCanonicalName()); cf.addImport(JsonSerializer.class.getCanonicalName()); cf.addImport(JavaScriptObject.class.getCanonicalName()); + cf.addImport(ResultDeserializer.class.getCanonicalName()); + cf.addImport(AsyncCallback.class.getCanonicalName()); cf.addImport(GWT.class.getCanonicalName()); cf.setSuperclass(AbstractJsonProxy.class.getSimpleName()); cf.addImplementedInterface(svcInf.getErasedType().getQualifiedSourceName()); @@ -220,6 +231,48 @@ class ProxyCreator { } } + private void generateProxyCallCreator(final TreeLogger logger, + final SourceWriter w) throws UnableToCompleteException { + String callName = getJsonCallClassName(logger); + w.println(); + w.println("@Override"); + w.print("protected <T> "); + w.print(callName); + w.print("<T> newJsonCall(final AbstractJsonProxy proxy, "); + w.print("final String methodName, final String reqData, "); + w.println("final ResultDeserializer<T> ser, final AsyncCallback<T> cb) {"); + w.indent(); + + w.print("return new "); + w.print(callName); + w.println("<T>(proxy, methodName, reqData, ser, cb);"); + + w.outdent(); + w.println("}"); + } + + private String getJsonCallClassName(final TreeLogger logger) + throws UnableToCompleteException { + RpcImpl impl = svcInf.getAnnotation(RpcImpl.class); + if (impl == null) { + return JsonCall11HttpPost.class.getCanonicalName(); + } else if (impl.version() == Version.V1_1 + && impl.transport() == Transport.HTTP_POST) { + return JsonCall11HttpPost.class.getCanonicalName(); + } else if (impl.version() == Version.V2_0 + && impl.transport() == Transport.HTTP_POST) { + return JsonCall20HttpPost.class.getCanonicalName(); + } else if (impl.version() == Version.V2_0 + && impl.transport() == Transport.HTTP_GET) { + return JsonCall20HttpGet.class.getCanonicalName(); + } + + logger.log(Type.ERROR, "Unsupported JSON-RPC version and transport " + + "combination: Supported are 1.1 over HTTP POST and " + + "2.0 over HTTP POST and GET"); + throw new UnableToCompleteException(); + } + private void generateProxyMethods(final TreeLogger logger, final SourceWriter srcWriter) { final JMethod[] methodList = svcInf.getOverridableMethods(); @@ -334,11 +387,12 @@ class ProxyCreator { final String reqDataStr; if (params.length == 1) { - reqDataStr = "\"\""; + reqDataStr = "\"[]\""; } else { final String reqData = nameFactory.createName("reqData"); w.println("final StringBuilder " + reqData + " = new StringBuilder();"); needsComma = false; + w.println(reqData + ".append('[');"); for (int i = 0; i < params.length - 1; i++) { if (needsComma) { w.println(reqData + ".append(\",\");"); @@ -376,6 +430,7 @@ class ProxyCreator { w.println("}"); } } + w.println(reqData + ".append(']');"); reqDataStr = reqData + ".toString()"; } |