package fr.lteconsulting.hexa.client.comm;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import com.google.gwt.core.client.JsArray;
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.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONString;
/*
* Purpose of this class is to send a nucket of requests,
* handle if the client wants to send pre- and post- network requests,
* and return the result to its caller
*/
public class RPCBatchRequestSender
{
public interface XRPCBatchRequestSender
{
void sent( RPCBatchRequestSender request );
void answerReceived( RPCBatchRequestSender request );
void error( RPCErrorCodes errorCode, Exception exception, RPCBatchRequestSender request );
List<BeforeNetworkRequestHandler> getBeforeNetworkRequestHandlers();
List<AfterNetworkRequestHandler> getAfterNetworkRequestHandlers();
}
private final int MAX_NB_REQUESTS = 20;
XRPCBatchRequestSender callback = null;
String url = null;
HashMap<String, ServiceInfo> usedServices = new HashMap<String, ServiceInfo>();
ArrayList<RequestCallInfo> requestsToSend = new ArrayList<RequestCallInfo>();
ArrayList<RequestCallInfo> sentRequests = null;
int nbSentBytes = 0;
Request sentRequest = null;
String receivedTxt = null;
// initialize with url, and other needed information
public void init( String baseUrl, XRPCBatchRequestSender callback )
{
this.url = baseUrl + "&locale=" + LocaleInfo.getCurrentLocale().getLocaleName();
this.callback = callback;
}
// returns true if this instance can send another RPC in this batch
// basically, once the request batch is prepared and sent, no more calls can
// be added
public boolean canAddRequest()
{
return sentRequest == null && requestsToSend.size() < MAX_NB_REQUESTS;
}
public boolean isReadyToSend()
{
return sentRequest == null;
}
public boolean isSending()
{
return sentRequest != null && receivedTxt == null;
}
// adds a request to the batch
public void addRequest( RequestCallInfo info )
{
checkCallService( info );
requestsToSend.add( info );
}
public String getReceivedText()
{
return receivedTxt;
}
public String getTrace()
{
return url;
}
public int getNbSentBytes()
{
return nbSentBytes;
}
public int getNbReceivedBytes()
{
if( receivedTxt == null )
return 0;
return receivedTxt.length();
}
// necessary to be sure that call's service is referenced
private void checkCallService( RequestCallInfo info )
{
String service = info.request.service;
String interfaceChecksum = info.request.interfaceChecksum;
String key = getServiceKey( info );
if( usedServices.containsKey( key ) )
return;
ServiceInfo serviceInfo = new ServiceInfo( service, interfaceChecksum );
serviceInfo.id = usedServices.size();
usedServices.put( key, serviceInfo );
}
private String getServiceKey( RequestCallInfo info )
{
return info.request.service + "-" + info.request.interfaceChecksum;
}
// sends the batch in the network
// when response arrives, it will be given back to each request callback
public boolean send()
{
assert sentRequests == null;
sentRequests = new ArrayList<RequestCallInfo>();
// add prepended requests,
addPrependedRequests( sentRequests );
// add requests to send
while( !requestsToSend.isEmpty() )
sentRequests.add( requestsToSend.remove( 0 ) );
// add appended requests
addAppendedRequests( sentRequests );
// prepare payload
JSONArray payload = createPayload();
RequestBuilder builderPost = buildMultipart( "payload", payload.toString() );
nbSentBytes += builderPost.getRequestData().length();
try
{
sentRequest = builderPost.send();
}
catch( RequestException e )
{
callback.error( RPCErrorCodes.ERROR_REQUEST_SEND, e, this );
return false;
}
callback.sent( this );
return true;
}
private final RequestCallback requestCallback = new RequestCallback()
{
@Override
public void onResponseReceived( Request request, Response response )
{
int statusCode = response.getStatusCode();
if( statusCode == 500 || statusCode == 0 )
{
callback.error( RPCErrorCodes.ERROR_REQUEST_RESPONSE_STATUS, null, RPCBatchRequestSender.this );
return;
}
receivedTxt = response.getText();
if( receivedTxt == null || receivedTxt.length() == 0 )
{
callback.error( RPCErrorCodes.ERROR_REQUEST_RESPONSE_EMPTY, null, RPCBatchRequestSender.this );
return;
}
assert statusCode == 200;
JsArray<GenericJSO> jso = null;
try
{
// parses the json encoded answer
// native : JSON.parse(jsonString);
// eval('(' + jsonString + ')');
jso = JsonUtils.unsafeEval( receivedTxt ).cast();
}
catch( Exception exception )
{
callback.error( RPCErrorCodes.ERROR_REQUEST_RESPONSE_PARSE, exception, RPCBatchRequestSender.this );
return;
}
int nbRequests = sentRequests.size();
int nbAnswers = jso.length();
assert nbRequests == nbAnswers;
for( int r = 0; r < nbRequests; r++ )
{
// pair each request with its answer
RequestCallInfo requestCallInfo = sentRequests.get( r );
GenericJSO rawAnswer = jso.get( r );
// decrypt the raw answer
int msgLevel = rawAnswer.getIntByIdx( 0 );
String msg = rawAnswer.getStringByIdx( 1 );
GenericJSO hangOut = rawAnswer.getGenericJSOByIdx( 2 );
ResponseJSO retValue = rawAnswer.<ResponseJSO>getJavaScriptObjectByIdx( 3 );
requestCallInfo.setResult( msgLevel, msg, hangOut, retValue );
}
callback.answerReceived( RPCBatchRequestSender.this );
}
@Override
public void onError( Request request, Throwable exception )
{
Exception e = null;
if( exception instanceof Exception )
e = (Exception) exception;
callback.error( RPCErrorCodes.ERROR_REQUEST_GWT, e, RPCBatchRequestSender.this );
}
};
// prepare a multipart form http request
private RequestBuilder buildMultipart( String name, String value )
{
String boundary = "AJAX------" + Math.random() + "" + new Date().getTime();
RequestBuilder builderPost = new RequestBuilder( RequestBuilder.POST, url );
builderPost.setHeader( "Content-Type", "multipart/form-data; charset=utf-8; boundary=" + boundary );
builderPost.setCallback( requestCallback );
String CRLF = "\r\n";
String data = "--" + boundary + CRLF;
data += "--" + boundary + CRLF;
data += "Content-Disposition: form-data; ";
data += "name=\"" + name + "\"" + CRLF + CRLF;
data += value + CRLF;
data += "--" + boundary + "--" + CRLF;
builderPost.setRequestData( data );
return builderPost;
}
private JSONArray createPayload()
{
// services
JSONArray servicesUsed = new JSONArray();
for( ServiceInfo service : usedServices.values() )
{
service.id = servicesUsed.size(); // re order services...
servicesUsed.set( service.id, service.getJson() );
}
// API Calls part of the url
JSONArray calls = new JSONArray();
for( RequestCallInfo info : sentRequests )
calls.set( calls.size(), serializeCall( info ) );
// final payload
JSONArray payload = new JSONArray();
payload.set( 0, servicesUsed );
payload.set( 1, calls );
return payload;
}
private JSONArray serializeCall( RequestCallInfo info )
{
ServiceInfo serviceInfo = usedServices.get( getServiceKey( info ) );
JSONArray json = info.request.getJson();
json.set( 2, new JSONNumber( serviceInfo.id ) );
return json;
}
private void addPrependedRequests( final List<RequestCallInfo> requestsList )
{
AcceptsRPCRequests prependerImpl = new AcceptsRPCRequests()
{
@Override
public void sendRequest( boolean fUseCache, boolean fInvalidate, RequestDesc request, Object cookie, XRPCRequest callback )
{
RequestCallInfo callInfo = new RequestCallInfo( request, callback, cookie );
checkCallService( callInfo );
requestsList.add( callInfo );
}
};
List<BeforeNetworkRequestHandler> prependers = callback.getBeforeNetworkRequestHandlers();
if( prependers == null )
return;
// let the prependers add their customized requests
for( BeforeNetworkRequestHandler prepender : prependers )
prepender.onBeforeNetworkRequest( prependerImpl );
}
private void addAppendedRequests( final List<RequestCallInfo> requestsList )
{
AcceptsRPCRequests appenderImpl = new AcceptsRPCRequests()
{
@Override
public void sendRequest( boolean fUseCache, boolean fInvalidate, RequestDesc request, Object cookie, XRPCRequest callback )
{
RequestCallInfo callInfo = new RequestCallInfo( request, callback, cookie );
checkCallService( callInfo );
requestsList.add( callInfo );
}
};
List<AfterNetworkRequestHandler> appenders = callback.getAfterNetworkRequestHandlers();
if( appenders == null )
return;
// let the appenders add their customized requests
for( AfterNetworkRequestHandler appender : appenders )
appender.onAfterNetworkRequest( appenderImpl );
}
private static class ServiceInfo
{
public ServiceInfo( String service, String interfaceChecksum )
{
this.service = service;
this.interfaceChecksum = interfaceChecksum;
}
public JSONArray getJson()
{
JSONArray json = new JSONArray();
json.set( 0, new JSONString( service ) );
json.set( 1, new JSONString( interfaceChecksum ) );
return json;
}
int id;
String service;
String interfaceChecksum;
}
}