New APIs in RequestBuilder to add the following three methods:

void setRequestData(String)
void setCallback(RequestCallback)
Request send()

Calling each of these methods in turn will have a similar effect to calling the existing method:

sendRequest(String, RequestCallback)

The main difference is that the additional setters allow the data to be stored in the RequestBuilder object.  This change will enable RPC to return a "ready to go" RequestBuilder for a client to tweak.

Review by: bobv


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2243 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/http/client/RequestBuilder.java b/user/src/com/google/gwt/http/client/RequestBuilder.java
index 07ef478..2e6a19c 100644
--- a/user/src/com/google/gwt/http/client/RequestBuilder.java
+++ b/user/src/com/google/gwt/http/client/RequestBuilder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -72,33 +72,37 @@
 
   private static final HTTPRequestImpl httpRequest = (HTTPRequestImpl) GWT.create(HTTPRequestImpl.class);
 
-  /*
+  private RequestCallback callback;
+
+  /**
    * Map of header name to value that will be added to the JavaScript
    * XmlHttpRequest object before sending a request.
    */
   private Map<String, String> headers;
 
-  /*
+  /**
    * HTTP method to use when opening an JavaScript XmlHttpRequest object
    */
-  private String httpMethod;
+  private final String httpMethod;
 
-  /*
+  /**
    * Password to use when opening an JavaScript XmlHttpRequest object
    */
   private String password;
 
-  /*
+  private String requestData;
+
+  /**
    * Timeout in milliseconds before the request timeouts and fails.
    */
   private int timeoutMillis;
 
-  /*
+  /**
    * URL to use when opening an JavaScript XmlHttpRequest object.
    */
-  private String url;
+  private final String url;
 
-  /*
+  /**
    * User to use when opening an JavaScript XmlHttpRequest object
    */
   private String user;
@@ -147,48 +151,52 @@
   /**
    * Sends an HTTP request based on the current builder configuration. If no
    * request headers have been set, the header "Content-Type" will be used with
-   * a value of "text/plain; charset=utf-8".
+   * a value of "text/plain; charset=utf-8". You must call
+   * {@link #setRequestData(String)} and {@link #setCallback(RequestCallback)}
+   * before calling this method.
+   * 
+   * @return a {@link Request} object that can be used to track the request
+   * @throws RequestException if the call fails to initiate
+   * @throws NullPointerException if a request callback has not been set
+   */
+  public Request send() throws RequestException {
+    StringValidator.throwIfNull("callback", callback);
+    return doSend(requestData, callback);
+  }
+
+  /**
+   * Sends an HTTP request based on the current builder configuration with the
+   * specified data and callback. If no request headers have been set, the
+   * header "Content-Type" will be used with a value of "text/plain;
+   * charset=utf-8". This method does not cache <code>requestData</code> or
+   * <code>callback</code>.
    * 
    * @param requestData the data to send as part of the request
    * @param callback the response handler to be notified when the request fails
    *          or completes
    * @return a {@link Request} object that can be used to track the request
+   * @throws NullPointerException if <code>callback</code> <code>null</code>
    */
   public Request sendRequest(String requestData, RequestCallback callback)
       throws RequestException {
+    StringValidator.throwIfNull("callback", callback);
+    return doSend(requestData, callback);
+  }
 
-    if (user == null && password != null) {
-      throw new IllegalStateException("A password is set, but no user is set");
-    }
+  /**
+   * Sets the response handler for this request. This method <b>must</b> be
+   * called before calling {@link #send()}.
+   * 
+   * @param callback the response handler to be notified when the request fails
+   *          or completes
+   * 
+   * @throws NullPointerException if <code>callback</code> is
+   *           <code>null</code>
+   */
+  public void setCallback(RequestCallback callback) {
+    StringValidator.throwIfNull("callback", callback);
 
-    JavaScriptObject xmlHttpRequest = httpRequest.createXmlHTTPRequest();
-    String openError;
-    if (password != null) {
-      openError = XMLHTTPRequest.open(xmlHttpRequest, httpMethod, url, true,
-          user, password);
-    } else if (user != null) {
-      openError = XMLHTTPRequest.open(xmlHttpRequest, httpMethod, url, true,
-          user);
-    } else {
-      openError = XMLHTTPRequest.open(xmlHttpRequest, httpMethod, url, true);
-    }
-    if (openError != null) {
-      RequestPermissionException requestPermissionException = new RequestPermissionException(url);
-      requestPermissionException.initCause(new RequestException(openError));
-      throw requestPermissionException;
-    }
-
-    setHeaders(xmlHttpRequest);
-
-    Request request = new Request(xmlHttpRequest, timeoutMillis, callback);
-
-    String sendError = XMLHTTPRequest.send(xmlHttpRequest, request,
-        requestData, callback);
-    if (sendError != null) {
-      throw new RequestException(sendError);
-    }
-
-    return request;
+    this.callback = callback;
   }
 
   /**
@@ -229,6 +237,16 @@
   }
 
   /**
+   * Sets the data to send as part of this request. This method <b>must</b> be
+   * called before calling {@link #send()}.
+   * 
+   * @param requestData the data to send as part of the request
+   */
+  public void setRequestData(String requestData) {
+    this.requestData = requestData;
+  }
+
+  /**
    * Sets the number of milliseconds to wait for a request to complete. Should
    * the request timeout, the
    * {@link com.google.gwt.http.client.RequestCallback#onError(Request, Throwable)}
@@ -264,6 +282,49 @@
     this.user = user;
   }
 
+  /**
+   * Sends an HTTP request based on the current builder configuration. If no
+   * request headers have been set, the header "Content-Type" will be used with
+   * a value of "text/plain; charset=utf-8".
+   * 
+   * @return a {@link Request} object that can be used to track the request
+   * @throws RequestException if the call fails to initiate
+   * @throws NullPointerException if request data has not been set
+   * @throws NullPointerException if a request callback has not been set
+   */
+  private Request doSend(String requestData, RequestCallback callback)
+      throws RequestException {
+    JavaScriptObject xmlHttpRequest = httpRequest.createXmlHTTPRequest();
+    String openError;
+    if (user != null && password != null) {
+      openError = XMLHTTPRequest.open(xmlHttpRequest, httpMethod, url, true,
+          user, password);
+    } else if (user != null) {
+      openError = XMLHTTPRequest.open(xmlHttpRequest, httpMethod, url, true,
+          user);
+    } else {
+      openError = XMLHTTPRequest.open(xmlHttpRequest, httpMethod, url, true);
+    }
+    if (openError != null) {
+      RequestPermissionException requestPermissionException = new RequestPermissionException(
+          url);
+      requestPermissionException.initCause(new RequestException(openError));
+      throw requestPermissionException;
+    }
+
+    setHeaders(xmlHttpRequest);
+
+    Request request = new Request(xmlHttpRequest, timeoutMillis, callback);
+
+    String sendError = XMLHTTPRequest.send(xmlHttpRequest, request,
+        requestData, callback);
+    if (sendError != null) {
+      throw new RequestException(sendError);
+    }
+
+    return request;
+  }
+
   /*
    * Internal method that actually sets our cached headers on the underlying
    * JavaScript XmlHttpRequest object. If there are no headers set, then we set
diff --git a/user/src/com/google/gwt/http/client/StringValidator.java b/user/src/com/google/gwt/http/client/StringValidator.java
index 1ef9e98..ee2b5ca 100644
--- a/user/src/com/google/gwt/http/client/StringValidator.java
+++ b/user/src/com/google/gwt/http/client/StringValidator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2008 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
@@ -57,11 +57,11 @@
    * Throws a {@link NullPointerException} if the value is <code>null</code>.
    * 
    * @param name the name of the value, used in error messages
-   * @param value the string value that needs to be validated
+   * @param value the value that needs to be validated
    * 
    * @throws NullPointerException if the value is <code>null</code>
    */
-  public static void throwIfNull(String name, String value) {
+  public static void throwIfNull(String name, Object value) {
     if (null == value) {
       throw new NullPointerException(name + " cannot be null");
     }
diff --git a/user/test/com/google/gwt/http/client/RequestBuilderTest.java b/user/test/com/google/gwt/http/client/RequestBuilderTest.java
index bedda28..5a7d84e 100644
--- a/user/test/com/google/gwt/http/client/RequestBuilderTest.java
+++ b/user/test/com/google/gwt/http/client/RequestBuilderTest.java
@@ -144,6 +144,51 @@
    * Test method for
    * {@link com.google.gwt.http.client.RequestBuilder#sendRequest(java.lang.String, com.google.gwt.http.client.RequestCallback)}.
    */
+  public void testSend_GET() throws RequestException {
+    delayTestFinish(TEST_FINISH_DELAY);
+
+    RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
+        getTestBaseURL() + "send_GET");
+    builder.setCallback(new RequestCallback() {
+      public void onError(Request request, Throwable exception) {
+        fail(exception.getMessage());
+      }
+
+      public void onResponseReceived(Request request, Response response) {
+        assertEquals(200, response.getStatusCode());
+        finishTest();
+      }
+    });
+    builder.send();
+  }
+
+  /**
+   * Test method for {@link com.google.gwt.http.client.RequestBuilder#send()}.
+   */
+  public void testSend_POST() throws RequestException {
+    delayTestFinish(TEST_FINISH_DELAY);
+
+    RequestBuilder builder = new RequestBuilder(RequestBuilder.POST,
+        getTestBaseURL() + "sendRequest_POST");
+    builder.setHeader("Content-Type", "application/x-www-form-urlencoded");
+    builder.setCallback(new RequestCallback() {
+      public void onError(Request request, Throwable exception) {
+        fail(exception.getMessage());
+      }
+
+      public void onResponseReceived(Request request, Response response) {
+        assertEquals(200, response.getStatusCode());
+        finishTest();
+      }
+    });
+    builder.setRequestData("method=test+request");
+    builder.send();
+  }
+
+  /**
+   * Test method for
+   * {@link com.google.gwt.http.client.RequestBuilder#sendRequest(java.lang.String, com.google.gwt.http.client.RequestCallback)}.
+   */
   public void testSendRequest_GET() throws RequestException {
     delayTestFinish(TEST_FINISH_DELAY);
 
@@ -183,22 +228,40 @@
     });
   }
 
+  public void testSetCallback() {
+    RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
+        getTestBaseURL());
+    try {
+      builder.setCallback(null);
+      fail("Expected NullPointerException");
+    } catch (NullPointerException expected) {
+    }
+  }
+
   public void testSetPassword() {
     RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
         getTestBaseURL());
     try {
       builder.setPassword(null);
-    } catch (NullPointerException ex) {
-      // Correct behavior, exception was thrown
+      fail("Expected NullPointerException");
+    } catch (NullPointerException expected) {
     }
 
     try {
       builder.setPassword("");
-    } catch (IllegalArgumentException ex) {
-      // Correct behavior, exception was thrown
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
     }
   }
 
+  public void testSetRequestData() {
+    RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
+        getTestBaseURL());
+    // Legal.
+    builder.setRequestData(null);
+    builder.setRequestData("");
+  }
+
   /**
    * Test method for
    * {@link com.google.gwt.http.client.RequestBuilder#setHeader(java.lang.String, java.lang.String)}.
@@ -219,29 +282,25 @@
     try {
       builder.setHeader(null, "bar");
       fail("setRequestHeader(null, \"bar\")");
-    } catch (NullPointerException ex) {
-      // purposely ignored
+    } catch (NullPointerException expected) {
     }
 
     try {
       builder.setHeader("", "bar");
       fail("setRequestHeader(\"\", \"bar\")");
-    } catch (IllegalArgumentException ex) {
-      // purposely ignored
+    } catch (IllegalArgumentException expected) {
     }
 
     try {
       builder.setHeader("foo", null);
       fail("setRequestHeader(\"foo\", null)");
-    } catch (NullPointerException ex) {
-      // purposely ignored
+    } catch (NullPointerException expected) {
     }
 
     try {
       builder.setHeader("foo", "");
       fail("setRequestHeader(\"foo\", \"\")");
-    } catch (IllegalArgumentException ex) {
-      // purposely ignored
+    } catch (IllegalArgumentException expected) {
     }
 
     delayTestFinish(TEST_FINISH_DELAY);
@@ -326,14 +385,14 @@
         getTestBaseURL());
     try {
       builder.setUser(null);
-    } catch (NullPointerException ex) {
-      // Correct behavior, exception was thrown
+      fail("Expected NullPointerException");
+    } catch (NullPointerException expected) {
     }
 
     try {
       builder.setUser("");
-    } catch (IllegalArgumentException ex) {
-      // Correct behavior, exception was thrown
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException expected) {
     }
   }
 }
diff --git a/user/test/com/google/gwt/http/server/RequestBuilderTestServlet.java b/user/test/com/google/gwt/http/server/RequestBuilderTestServlet.java
index 38b93dd..f0efd75 100644
--- a/user/test/com/google/gwt/http/server/RequestBuilderTestServlet.java
+++ b/user/test/com/google/gwt/http/server/RequestBuilderTestServlet.java
@@ -51,6 +51,10 @@
       } else {
         response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
       }
+    } else if (pathInfo.equals(getPathInfoBase() + "send_GET")) {
+      response.setStatus(HttpServletResponse.SC_OK);
+      response.getWriter().write("<html><body>hello</body></html>");
+      response.setContentType("text/html");
     } else if (pathInfo.equals(getPathInfoBase() + "sendRequest_GET")) {
       response.setStatus(HttpServletResponse.SC_OK);
       response.getWriter().write("<html><body>hello</body></html>");