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() {