blob: 27685cd5a25e2367593bdd0d696fc4e356cc3363 [file] [log] [blame]
/*
* 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
* 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.http.client;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.xhr.client.ReadyStateChangeHandler;
import com.google.gwt.xhr.client.XMLHttpRequest;
import java.util.HashMap;
import java.util.Map;
/**
* Builder for constructing {@link com.google.gwt.http.client.Request} objects.
*
* <h3>Required Module</h3> Modules that use this class should inherit
* <code>com.google.gwt.http.HTTP</code>.
*
* {@gwt.include
* com/google/gwt/examples/http/InheritsExample.gwt.xml}
*
*/
public class RequestBuilder {
/**
* HTTP request method constants.
*/
public static final class Method {
private final String name;
private Method(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
/**
* Specifies that the HTTP DELETE method should be used.
*/
public static final Method DELETE = new Method("DELETE");
/**
* Specifies that the HTTP GET method should be used.
*/
public static final Method GET = new Method("GET");
/**
* Specifies that the HTTP HEAD method should be used.
*/
public static final Method HEAD = new Method("HEAD");
/**
* Specifies that the HTTP POST method should be used.
*/
public static final Method POST = new Method("POST");
/**
* Specifies that the HTTP PUT method should be used.
*/
public static final Method PUT = new Method("PUT");
/**
* The callback to call when the request completes.
*/
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 a JavaScript XmlHttpRequest object.
*/
private final String httpMethod;
/**
* Password to use when opening a JavaScript XmlHttpRequest object.
*/
private String password;
/**
* Request data to use when sending a JavaScript XmlHttpRequest object.
*/
private String requestData;
/**
* Timeout in milliseconds before the request timeouts and fails.
*/
private int timeoutMillis;
/**
* URL to use when opening a JavaScript XmlHttpRequest object.
*/
private final String url;
/**
* User to use when opening a JavaScript XmlHttpRequest object.
*/
private String user;
/**
* Creates a builder using the parameters for configuration.
*
* @param httpMethod HTTP method to use for the request
* @param url URL that has already has already been encoded. Please see
* {@link com.google.gwt.http.client.URL#encode(String)},
* {@link com.google.gwt.http.client.URL#encodePathSegment(String)} and
* {@link com.google.gwt.http.client.URL#encodeQueryString(String)} for
* how to do this.
* @throws IllegalArgumentException if the httpMethod or URL are empty
* @throws NullPointerException if the httpMethod or the URL are null
*/
public RequestBuilder(Method httpMethod, String url) {
this((httpMethod == null) ? null : httpMethod.toString(), url);
}
/**
* Creates a builder using the parameters values for configuration.
*
* @param httpMethod HTTP method to use for the request
* @param url URL that has already has already been URL encoded. Please see
* {@link com.google.gwt.http.client.URL#encode(String)} and
* {@link com.google.gwt.http.client.URL#encodePathSegment(String)} and
* {@link com.google.gwt.http.client.URL#encodeQueryString(String)} for
* how to do this.
* @throws IllegalArgumentException if the httpMethod or URL are empty
* @throws NullPointerException if the httpMethod or the URL are null
*/
protected RequestBuilder(String httpMethod, String url) {
StringValidator.throwIfEmptyOrNull("httpMethod", httpMethod);
StringValidator.throwIfEmptyOrNull("url", url);
this.httpMethod = httpMethod;
this.url = url;
}
/**
* Returns the callback previously set by
* {@link #setCallback(RequestCallback)}, or <code>null</code> if no callback
* was set.
*/
public RequestCallback getCallback() {
return callback;
}
/**
* Returns the value of a header previous set by
* {@link #setHeader(String, String)}, or <code>null</code> if no such header
* was set.
*
* @param header the name of the header
*/
public String getHeader(String header) {
if (headers == null) {
return null;
}
return headers.get(header);
}
/**
* Returns the HTTP method specified in the constructor.
*/
public String getHTTPMethod() {
return httpMethod;
}
/**
* Returns the password previously set by {@link #setPassword(String)}, or
* <code>null</code> if no password was set.
*/
public String getPassword() {
return password;
}
/**
* Returns the requestData previously set by {@link #setRequestData(String)},
* or <code>null</code> if no requestData was set.
*/
public String getRequestData() {
return requestData;
}
/**
* Returns the timeoutMillis previously set by {@link #setTimeoutMillis(int)},
* or <code>0</code> if no timeoutMillis was set.
*/
public int getTimeoutMillis() {
return timeoutMillis;
}
/**
* Returns the HTTP URL specified in the constructor.
*/
public String getUrl() {
return url;
}
/**
* Returns the user previously set by {@link #setUser(String)}, or
* <code>null</code> if no user was set.
*/
public String getUser() {
return 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". 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);
}
/**
* 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);
this.callback = callback;
}
/**
* Sets a request header with the given name and value. If a header with the
* specified name has already been set then the new value overwrites the
* current value.
*
* @param header the name of the header
* @param value the value of the header
*
* @throws NullPointerException if header or value are null
* @throws IllegalArgumentException if header or value are the empty string
*/
public void setHeader(String header, String value) {
StringValidator.throwIfEmptyOrNull("header", header);
StringValidator.throwIfEmptyOrNull("value", value);
if (headers == null) {
headers = new HashMap<String, String>();
}
headers.put(header, value);
}
/**
* Sets the password to use in the request URL. This is ignored if there is no
* user specified.
*
* @param password password to use in the request URL
*
* @throws IllegalArgumentException if the password is empty
* @throws NullPointerException if the password is null
*/
public void setPassword(String password) {
StringValidator.throwIfEmptyOrNull("password", password);
this.password = password;
}
/**
* 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)}
* method will be called on the callback instance given to the
* {@link com.google.gwt.http.client.RequestBuilder#sendRequest(String, RequestCallback)}
* method. The callback method will receive an instance of the
* {@link com.google.gwt.http.client.RequestTimeoutException} class as its
* {@link java.lang.Throwable} argument.
*
* @param timeoutMillis number of milliseconds to wait before canceling the
* request, a value of zero disables timeouts
*
* @throws IllegalArgumentException if the timeout value is negative
*/
public void setTimeoutMillis(int timeoutMillis) {
if (timeoutMillis < 0) {
throw new IllegalArgumentException("Timeouts cannot be negative");
}
this.timeoutMillis = timeoutMillis;
}
/**
* Sets the user name that will be used in the request URL.
*
* @param user user name to use
* @throws IllegalArgumentException if the user is empty
* @throws NullPointerException if the user is null
*/
public void setUser(String user) {
StringValidator.throwIfEmptyOrNull("user", user);
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, final RequestCallback callback)
throws RequestException {
XMLHttpRequest xmlHttpRequest = XMLHttpRequest.create();
try {
if (user != null && password != null) {
xmlHttpRequest.open(httpMethod, url, user, password);
} else if (user != null) {
xmlHttpRequest.open(httpMethod, url, user);
} else {
xmlHttpRequest.open(httpMethod, url);
}
} catch (JavaScriptException e) {
RequestPermissionException requestPermissionException = new RequestPermissionException(
url);
requestPermissionException.initCause(new RequestException(e.getMessage()));
throw requestPermissionException;
}
setHeaders(xmlHttpRequest);
final Request request = new Request(xmlHttpRequest, timeoutMillis, callback);
// Must set the onreadystatechange handler before calling send().
xmlHttpRequest.setOnReadyStateChange(new ReadyStateChangeHandler() {
public void onReadyStateChange(XMLHttpRequest xhr) {
if (xhr.getReadyState() == XMLHttpRequest.DONE) {
xhr.clearOnReadyStateChange();
request.fireOnResponseReceived(callback);
}
}
});
try {
xmlHttpRequest.send(requestData);
} catch (JavaScriptException e) {
throw new RequestException(e.getMessage());
}
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
* the "Content-Type" to "text/plain; charset=utf-8". This is really lining us
* up for integration with RPC.
*/
private void setHeaders(XMLHttpRequest xmlHttpRequest)
throws RequestException {
if (headers != null && headers.size() > 0) {
for (Map.Entry<String, String> header : headers.entrySet()) {
try {
xmlHttpRequest.setRequestHeader(header.getKey(), header.getValue());
} catch (JavaScriptException e) {
throw new RequestException(e.getMessage());
}
}
} else {
xmlHttpRequest.setRequestHeader("Content-Type",
"text/plain; charset=utf-8");
}
}
}