Extracts a configuration API for use by RemoteServiceProxy.
Patch by: bobv
Review by: rjrjr
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5045 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/rpc/RpcRequestBuilder.java b/user/src/com/google/gwt/user/client/rpc/RpcRequestBuilder.java
new file mode 100644
index 0000000..387fa9a
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/rpc/RpcRequestBuilder.java
@@ -0,0 +1,212 @@
+/*
+ * 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.user.client.rpc;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.http.client.RequestBuilder;
+import com.google.gwt.http.client.RequestCallback;
+
+/**
+ * This class encapsulates the logic necessary to configure a RequestBuilder for
+ * use with an RPC proxy object. Users who wish to alter the specifics of the
+ * HTTP requests issued by RPC proxy objects may override the protected
+ * <code>doXyz</code> methods and pass an instance of the subclass to
+ * {@link ServiceDefTarget#setRpcRequestBuilder}.
+ */
+public class RpcRequestBuilder {
+ /**
+ * Used by {@link #doSetContentType}.
+ */
+ public static final String CONTENT_TYPE_HEADER = "Content-Type";
+
+ /**
+ * Used by {@link #doFinish}.
+ */
+ /*
+ * NB: Keep in sync with RemoteServiceServlet.
+ */
+ public static final String STRONG_NAME_HEADER = "X-GWT-Permutation";
+
+ /**
+ * Not exposed directly to the subclass.
+ */
+ private RequestBuilder builder;
+
+ /**
+ * Initialize the RpcRequestBuilder. This method must be called before any of
+ * the other methods in this class may be called. Calling <code>create</code>
+ * before calling {@link #finish()} will reset the state of the
+ * RpcRequestBuilder.
+ * <p>
+ * This method delegates to {@link #doCreate} to instantiate the
+ * RequestBuilder.
+ *
+ * @param serviceEntryPoint The URL entry point
+ * @return <code>this</code>
+ * @see ServiceDefTarget#setServiceEntryPoint(String)
+ */
+ public final RpcRequestBuilder create(String serviceEntryPoint) {
+ builder = doCreate(serviceEntryPoint);
+ assert builder != null : "doCreate failed to return a RequestBuilder";
+ return this;
+ }
+
+ /**
+ * This method must be called to return the RequestBuilder that the RPC
+ * request will be made with.
+ * <p>
+ * This method will call {@link #doFinish} before returning the current
+ * RequestBuilder.
+ */
+ public final RequestBuilder finish() {
+ try {
+ assert builder != null : "Call create() first";
+ doFinish(builder);
+ return builder;
+ } finally {
+ builder = null;
+ }
+ }
+
+ /**
+ * Sets the RequestCallback to be used by the RequestBuilder. Delegates to
+ * {@link #doSetCallback}.
+ *
+ * @param callback the RequestCallback to be used by the RequestBuilder
+ * @return <code>this</code>
+ */
+ public final RpcRequestBuilder setCallback(RequestCallback callback) {
+ assert builder != null : "Call create() first";
+ doSetCallback(builder, callback);
+ return this;
+ }
+
+ /**
+ * Sets the MIME content type to be used by the RequestBuilder. Delegates to
+ * {@link #doSetContentType}.
+ *
+ * @param contentType the MIME content type to be used in the request
+ * @return <code>this</code>
+ */
+ public final RpcRequestBuilder setContentType(String contentType) {
+ assert builder != null : "Call create() first";
+ doSetContentType(builder, contentType);
+ return this;
+ }
+
+ /**
+ * Sets the request data to be sent in the request. Delegates to
+ * {@link #doSetRequestData}.
+ *
+ * @param data the data to send
+ * @return <code>this</code>
+ */
+ public final RpcRequestBuilder setRequestData(String data) {
+ assert builder != null : "Call create() first";
+ doSetRequestData(builder, data);
+ return this;
+ }
+
+ /**
+ * Sets the request id of the request. Delegates to {@link #doSetRequestId}.
+ *
+ * @param id the issue number of the request
+ * @return <code>this</code>
+ */
+ public final RpcRequestBuilder setRequestId(int id) {
+ assert builder != null : "Call create() first";
+ doSetRequestId(builder, id);
+ return this;
+ }
+
+ /**
+ * Called by {@link #create} to instantiate the RequestBuilder object.
+ * <p>
+ * The default implementation creates a <code>POST</code> RequestBuilder with
+ * the given entry point.
+ *
+ * @param serviceEntryPoint the URL to which the request should be issued
+ * @return the RequestBuilder that should be ultimately passed to the
+ * RpcRequestBuilder's caller.
+ */
+ protected RequestBuilder doCreate(String serviceEntryPoint) {
+ return new RequestBuilder(RequestBuilder.POST, serviceEntryPoint);
+ }
+
+ /**
+ * Called by {@link #finish()} prior to returning the RequestBuilder to the
+ * caller.
+ * <p>
+ * The default implementation sets the {@value #STRONG_NAME_HEADER} header to
+ * the value returned by {@link GWT#getPermutationStrongName()}.
+ *
+ * @param rb The RequestBuilder that is currently being configured
+ */
+ protected void doFinish(RequestBuilder rb) {
+ rb.setHeader(STRONG_NAME_HEADER, GWT.getPermutationStrongName());
+ }
+
+ /**
+ * Called by {@link #setCallback}.
+ * <p>
+ * The default implementation calls
+ * {@link RequestBuilder#setCallback(RequestCallback)}.
+ *
+ * @param rb the RequestBuilder that is currently being configured
+ * @param callback the user-provided callback
+ */
+ protected void doSetCallback(RequestBuilder rb, RequestCallback callback) {
+ rb.setCallback(callback);
+ }
+
+ /**
+ * Called by {@link #setContentType}.
+ * <p>
+ * The default implementation sets the {@value #CONTENT_TYPE_HEADER} header to
+ * the value specified by <code>contentType</code> by calling
+ * {@link RequestBuilder#setHeader(String, String)}.
+ *
+ * @param rb the RequestBuilder that is currently being configured
+ * @param contentType the desired MIME type of the request's contents
+ */
+ protected void doSetContentType(RequestBuilder rb, String contentType) {
+ rb.setHeader(CONTENT_TYPE_HEADER, contentType);
+ }
+
+ /**
+ * Called by {@link #setRequestData}.
+ * <p>
+ * The default implementation invokes
+ * {@link RequestBuilder#setRequestData(String)}.
+ *
+ * @param rb the RequestBuilder that is currently being configured
+ * @param data the data to send
+ */
+ protected void doSetRequestData(RequestBuilder rb, String data) {
+ rb.setRequestData(data);
+ }
+
+ /**
+ * Called by {@link #setRequestId}.
+ * <p>
+ * The default implementation is a no-op.
+ *
+ * @param rb the RequestBuilder that is currently being configured
+ * @param id the request's issue id
+ */
+ protected void doSetRequestId(RequestBuilder rb, int id) {
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/rpc/ServiceDefTarget.java b/user/src/com/google/gwt/user/client/rpc/ServiceDefTarget.java
index acef7c8..b2084e6 100644
--- a/user/src/com/google/gwt/user/client/rpc/ServiceDefTarget.java
+++ b/user/src/com/google/gwt/user/client/rpc/ServiceDefTarget.java
@@ -18,8 +18,8 @@
/**
* An interface implemented by client-side RPC proxy objects. Cast the object
* returned from {@link com.google.gwt.core.client.GWT#create(Class)} on a
- * {@link RemoteService} should be cast to this interface to initialize the
- * target URL for the remote service.
+ * {@link RemoteService} to this interface to initialize the target URL for the
+ * remote service.
*/
public interface ServiceDefTarget {
@@ -43,6 +43,14 @@
String getServiceEntryPoint();
/**
+ * Sets the RpcRequestBuilder that should be used by the service
+ * implementation. This method can be called if customized request behavior is
+ * desired. Calling this method with a null value will reset any custom
+ * behavior to the default implementation.
+ */
+ void setRpcRequestBuilder(RpcRequestBuilder builder);
+
+ /**
* Sets the URL of a service implementation.
*
* @param address a URL that designates the service implementation to call
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/RemoteServiceProxy.java b/user/src/com/google/gwt/user/client/rpc/impl/RemoteServiceProxy.java
index cd2ae19..daea93f 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/RemoteServiceProxy.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/RemoteServiceProxy.java
@@ -15,13 +15,13 @@
*/
package com.google.gwt.user.client.rpc.impl;
-import com.google.gwt.core.client.GWT;
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.RequestException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.InvocationException;
+import com.google.gwt.user.client.rpc.RpcRequestBuilder;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamFactory;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
@@ -37,9 +37,9 @@
ServiceDefTarget {
/**
- * NB: Keep in sync with RemoteServiceServlet.
+ * The content type to be used in HTTP requests.
*/
- private static final String STRONG_NAME_HEADER = "X-GWT-Permutation";
+ private static final String RPC_CONTENT_TYPE = "text/x-gwt-rpc; charset=utf-8";
/**
* A global id to track any given request.
@@ -56,9 +56,6 @@
/**
* Indicates if RPC statistics should be gathered.
*/
- /**
- * Indicates if RPC statistics should be gathered.
- */
public static native boolean isStatsAvailable() /*-{
return !!$stats;
}-*/;
@@ -71,7 +68,8 @@
return $stats(data);
}-*/;
- public static native JavaScriptObject timeStat(String method, int count, String eventType) /*-{
+ public static native JavaScriptObject timeStat(String method, int count,
+ String eventType) /*-{
return {
moduleName: @com.google.gwt.core.client.GWT::getModuleName()(),
subSystem: 'rpc',
@@ -86,17 +84,21 @@
return requestId++;
}
+ /**
+ * @deprecated Use {@link RpcRequestBuilder} instead.
+ */
+ @Deprecated
protected static int getRequestId() {
return requestId;
}
/**
- * Return <code>true</code> if the encoded response contains a value
- * returned by the method invocation.
+ * Return <code>true</code> if the encoded response contains a value returned
+ * by the method invocation.
*
* @param encodedResponse
- * @return <code>true</code> if the encoded response contains a value
- * returned by the method invocation
+ * @return <code>true</code> if the encoded response contains a value returned
+ * by the method invocation
*/
static boolean isReturnValue(String encodedResponse) {
return encodedResponse.startsWith("//OK");
@@ -135,11 +137,13 @@
private final String moduleBaseURL;
/**
- * URL of the
- * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService}.
+ * URL of the {@link com.google.gwt.user.client.rpc.RemoteService
+ * RemoteService}.
*/
private String remoteServiceURL;
+ private RpcRequestBuilder rpcRequestBuilder;
+
/**
* The name of the serialization policy file specified during construction.
*/
@@ -169,13 +173,12 @@
}
/**
- * Returns a
- * {@link com.google.gwt.user.client.rpc.SerializationStreamReader SerializationStreamReader}
- * that is ready for reading.
+ * Returns a {@link com.google.gwt.user.client.rpc.SerializationStreamReader
+ * SerializationStreamReader} that is ready for reading.
*
* @param encoded string that encodes the response of an RPC request
- * @return {@link com.google.gwt.user.client.rpc.SerializationStreamReader SerializationStreamReader}
- * that is ready for reading
+ * @return {@link com.google.gwt.user.client.rpc.SerializationStreamReader
+ * SerializationStreamReader} that is ready for reading
* @throws SerializationException
*/
public ClientSerializationStreamReader createStreamReader(String encoded)
@@ -187,14 +190,14 @@
}
/**
- * Returns a
- * {@link com.google.gwt.user.client.rpc.SerializationStreamWriter SerializationStreamWriter}
- * that has had {@link ClientSerializationStreamWriter#prepareToWrite()}
- * called on it and it has already had had the name of the remote service
- * interface written as well.
+ * Returns a {@link com.google.gwt.user.client.rpc.SerializationStreamWriter
+ * SerializationStreamWriter} that has had
+ * {@link ClientSerializationStreamWriter#prepareToWrite()} called on it and
+ * it has already had had the name of the remote service interface written as
+ * well.
*
- * @return {@link com.google.gwt.user.client.rpc.SerializationStreamWriter SerializationStreamWriter}
- * that has had
+ * @return {@link com.google.gwt.user.client.rpc.SerializationStreamWriter
+ * SerializationStreamWriter} that has had
* {@link ClientSerializationStreamWriter#prepareToWrite()} called on
* it and it has already had had the name of the remote service
* interface written as well
@@ -213,6 +216,10 @@
return remoteServiceURL;
}
+ public void setRpcRequestBuilder(RpcRequestBuilder builder) {
+ this.rpcRequestBuilder = builder;
+ }
+
/**
* @see ServiceDefTarget#setServiceEntryPoint(String)
*/
@@ -221,7 +228,8 @@
}
/**
- * Performs a remote service method invocation.
+ * Performs a remote service method invocation. This method is called by
+ * generated proxy classes.
*
* @param <T> return type for the AsyncCallback
* @param responseReader instance used to read the return value of the
@@ -249,7 +257,7 @@
} finally {
if (RemoteServiceProxy.isStatsAvailable()
&& RemoteServiceProxy.stats(RemoteServiceProxy.bytesStat(methodName,
- invocationCount, requestData.length(), "requestSent"))) {
+ invocationCount, requestData.length(), "requestSent"))) {
}
}
return null;
@@ -303,13 +311,19 @@
RequestCallbackAdapter<T> responseHandler = new RequestCallbackAdapter<T>(
this, methodName, invocationCount, callback, responseReader);
- RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,
- getServiceEntryPoint());
+ ensureRpcRequestBuilder();
- rb.setHeader("Content-Type", "text/x-gwt-rpc; charset=utf-8");
- rb.setHeader(STRONG_NAME_HEADER, GWT.getPermutationStrongName());
- rb.setCallback(responseHandler);
- rb.setRequestData(requestData);
- return rb;
+ rpcRequestBuilder.create(getServiceEntryPoint());
+ rpcRequestBuilder.setCallback(responseHandler);
+ rpcRequestBuilder.setContentType(RPC_CONTENT_TYPE);
+ rpcRequestBuilder.setRequestData(requestData);
+ rpcRequestBuilder.setRequestId(invocationCount);
+ return rpcRequestBuilder.finish();
+ }
+
+ private void ensureRpcRequestBuilder() {
+ if (rpcRequestBuilder == null) {
+ rpcRequestBuilder = new RpcRequestBuilder();
+ }
}
}
diff --git a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
index fdabbb1..85b559d 100644
--- a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
+++ b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
@@ -41,9 +41,12 @@
SerializationPolicyProvider {
/**
+ * Used by {@link #getPermutationStrongName()}.
+ */
+ /*
* NB: Keep in sync with RemoteServiceProxy.
*/
- private static final String STRONG_NAME_HEADER = "X-GWT-Permutation";
+ protected static final String STRONG_NAME_HEADER = "X-GWT-Permutation";
private final ThreadLocal<HttpServletRequest> perThreadRequest = new ThreadLocal<HttpServletRequest>();
diff --git a/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java b/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java
index 37011ca..5811de7 100644
--- a/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java
@@ -18,6 +18,7 @@
import com.google.gwt.core.client.GWT;
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.junit.client.GWTTestCase;
@@ -36,6 +37,60 @@
* </p>
*/
public class RemoteServiceServletTest extends GWTTestCase {
+ private static class MyRpcRequestBuilder extends RpcRequestBuilder {
+ private boolean doCreate;
+ private boolean doFinish;
+ private boolean doSetCallback;
+ private boolean doSetContentType;
+ private boolean doSetRequestData;
+ private boolean doSetRequestId;
+
+ public void check() {
+ assertTrue("doCreate", doCreate);
+ assertTrue("doFinish", doFinish);
+ assertTrue("doSetCallback", doSetCallback);
+ assertTrue("doSetContentType", doSetContentType);
+ assertTrue("doSetRequestData", doSetRequestData);
+ assertTrue("doSetRequestId", doSetRequestId);
+ }
+
+ @Override
+ protected RequestBuilder doCreate(String serviceEntryPoint) {
+ doCreate = true;
+ return super.doCreate(serviceEntryPoint);
+ }
+
+ @Override
+ protected void doFinish(RequestBuilder rb) {
+ doFinish = true;
+ super.doFinish(rb);
+ }
+
+ @Override
+ protected void doSetCallback(RequestBuilder rb, RequestCallback callback) {
+ doSetCallback = true;
+ super.doSetCallback(rb, callback);
+ }
+
+ @Override
+ protected void doSetContentType(RequestBuilder rb, String contentType) {
+ doSetContentType = true;
+ super.doSetContentType(rb, contentType);
+ }
+
+ @Override
+ protected void doSetRequestData(RequestBuilder rb, String data) {
+ doSetRequestData = true;
+ super.doSetRequestData(rb, data);
+ }
+
+ @Override
+ protected void doSetRequestId(RequestBuilder rb, int id) {
+ doSetRequestId = true;
+ super.doSetRequestId(rb, id);
+ }
+ }
+
private static final int TEST_DELAY = 10000;
protected static RemoteServiceServletTestServiceAsync getAsyncService() {
@@ -117,6 +172,27 @@
finishTest();
}
});
+ };
+
+ /**
+ * Ensure that each doFoo method is called.
+ */
+ public void testRpcRequestBuilder() {
+ final MyRpcRequestBuilder builder = new MyRpcRequestBuilder();
+ RemoteServiceServletTestServiceAsync service = getAsyncService();
+ ((ServiceDefTarget) service).setRpcRequestBuilder(builder);
+
+ delayTestFinish(TEST_DELAY);
+ service.test(new AsyncCallback<Void>() {
+ public void onFailure(Throwable caught) {
+ TestSetValidator.rethrowException(caught);
+ }
+
+ public void onSuccess(Void result) {
+ builder.check();
+ finishTest();
+ }
+ });
}
public void testServiceInterfaceLocation() {