package org.swellrt.beta.client.operation;
import org.swellrt.beta.client.ServiceContext;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsonUtils;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestBuilder.Method;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import jsinterop.annotations.JsProperty;
import jsinterop.annotations.JsType;
/**
* A base class for operations which performs HTTP requests. The underlying HTTP client is platform dependent.
*
* @author pablojan@gmail.com (Pablo Ojanguren)
*
* @param <O> operation options
* @param <R> operation callback
*/
public abstract class HTTPOperation<O extends Operation.Options, R extends Operation.Response> implements Operation<O, R> {
@SuppressWarnings("serial")
public static class HTTPOperationException extends Exception {
private final int statusCode;
private final String statusMessage;
public int getStatusCode() {
return statusCode;
}
public String getStatusMessage() {
return statusMessage;
}
public HTTPOperationException(int statusCode, String statusMessage) {
this.statusCode = statusCode;
this.statusMessage = statusMessage;
}
}
@JsType(isNative = true)
private interface ResponseError {
@JsProperty
public String getError();
}
private final static String HEADER_WINDOW_ID = "X-window-id";
private final static String PARAM_URL_SESSION_ID = "sid";
private final static String PARAM_URL_TRANSIENT_SESSION_ID = "tid";
private final static int STATUS_READY = 0;
private final static int STATUS_IN_PROGRESS = 1;
private final static int STATUS_COMPLETED = 2;
private final static String querySeparator = "?";
private final static String queryAmp = "&";
private final static String queryEquals = "=";
private final static String pathSeparator = "/";
private final static String requestContext = "swell";
private final static String headerContentType = "Content-Type";
private final static String headerContentTypeValue = "text/plain; charset=utf-8";
private String path = requestContext;
private String query = "";
private String body = null;
private int status = STATUS_READY;
private final ServiceContext context;
private Callback<R> callback;
private boolean sessionInURL= true;
protected HTTPOperation(ServiceContext context) {
this.context = context;
}
/**
* Set to false to not propagate session id in URLs never (no URL rewriting).
* By the default this this flag is true: if session can't be sent as
* cookie, URL rewriting is done.
* @param state
*/
protected void setSessionInURLFlag(boolean state) {
sessionInURL = state;
}
protected HTTPOperation<O, R> addPathElement(String element) {
//Preconditions.checkArgument(element != null && !element.isEmpty(), "Empty string");
path += pathSeparator+element;
return this;
}
protected HTTPOperation<O, R> addQueryParam(String param, String value) {
if (query.isEmpty())
query += querySeparator;
else
query += queryAmp;
// TODO encode params
query += param + queryEquals + value;
return this;
}
protected HTTPOperation<O, R> setBody(String body) {
this.body = body;
return this;
}
private RequestBuilder getRequest(Method method, String url) {
RequestBuilder rb = new RequestBuilder(method, url);
rb.setIncludeCredentials(true);
rb.setHeader(headerContentType, headerContentTypeValue);
String windowId = getServiceContext().getWindowId();
if (windowId != null)
rb.setHeader(HEADER_WINDOW_ID, windowId);
return rb;
}
private String buildUrl() {
String url = getServiceContext().getHTTPAddress();
if (!url.endsWith(pathSeparator))
url += pathSeparator;
url += path;
if (sessionInURL &&
!getServiceContext().isSessionCookieAvailable()) {
if (getServiceContext().getTransientSessionId() != null)
url += ";" + PARAM_URL_TRANSIENT_SESSION_ID + "=" + getServiceContext().getTransientSessionId();
if (getServiceContext().getSessionId() != null)
url += ";" + PARAM_URL_SESSION_ID + "=" + getServiceContext().getSessionId();
}
url += query;
return url;
}
private void executeHttp(Method method) {
//Preconditions.checkArgument(status == STATUS_READY, "HTTP operations only can be executed once");
status = STATUS_IN_PROGRESS;
RequestBuilder requestBuilder = getRequest(method, buildUrl());
try {
requestBuilder.sendRequest(body, new RequestCallback() {
@Override
public void onError(Request request, Throwable exception) {
// TODO filter or wrap exception
HTTPOperation.this.status = STATUS_COMPLETED;
HTTPOperation.this.onError(exception, callback);
}
@Override
public void onResponseReceived(Request request,
com.google.gwt.http.client.Response response) {
HTTPOperation.this.status = STATUS_COMPLETED;
if (response.getStatusCode() != 200) {
String statusMessage = response.getStatusText();
try {
ResponseError e = JsonUtils.safeEval(response.getText());
statusMessage = e.getError();
} catch (IllegalArgumentException ex) {
}
HTTPOperation.this.onError(new HTTPOperationException(response.getStatusCode(), statusMessage), callback);
} else {
HTTPOperation.this.onSuccess(response.getStatusCode(), response.getText(), callback);
}
}
});
} catch (RequestException e) {
// TODO filter or wrap exception
HTTPOperation.this.status = STATUS_COMPLETED;
HTTPOperation.this.onError(e, callback);
} catch (RuntimeException e) {
// TODO filter or wrap exception
HTTPOperation.this.status = STATUS_COMPLETED;
HTTPOperation.this.onError(e, callback);
}
}
protected void executePost(Callback<R> callback) {
this.callback = callback;
executeHttp(RequestBuilder.POST);
}
protected void executeGet(Callback<R> callback) {
this.callback = callback;
executeHttp(RequestBuilder.GET);
}
protected void executeDelete(Callback<R> callback) {
this.callback = callback;
executeHttp(RequestBuilder.DELETE);
}
protected ServiceContext getServiceContext() {
return context;
}
protected abstract void onError(Throwable exception, Callback<R> callback);
protected abstract void onSuccess(int statusCode, String data, Callback<R> callback);
protected String generateBody(O options) {
if (options != null) {
if (options instanceof JavaScriptObject) {
return JsonUtils.stringify((JavaScriptObject) options);
}
}
return "Options couldn't be serialized to JSON";
}
@SuppressWarnings("unchecked")
protected R generateResponse(String json) {
return (R) JsonUtils.safeEval(json);
}
@Override
public abstract void execute(O options, Callback<R> callback);
}