package JsonClient.Java.async;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import JsonClient.Java.JsonClient;
import JsonClient.Java.urlstuff.UrlHelper;
/**
* Class representing a single async call to the api.<br />
* Allows for you to register individual events per call.
*
* @author Justin Nelson
*
*/
public class AsyncApiCall {
private String baseUrl;
private JsonClient client;
private ExecutorService exec;
private Map<String, String> initialParams;
private List<CallBack> callBaks;
private Future<Object> future;
private boolean done;
private boolean started;
protected AsyncApiCall(String baseUrl, Map<String, String> initialParams,
JsonClient client, ExecutorService exec) {
this.baseUrl = baseUrl;
this.client = client;
this.initialParams = initialParams;
this.exec = exec;
callBaks = new ArrayList<CallBack>();
done = false;
started = false;
}
/**
* Adds a new callback to the call. You are allowed to add a new callback
* after the call has been started, but not after it is done.
*
* @param cb
*/
public synchronized void addCallCompleteListener(CallBack cb) {
if (cb == null)
throw new NullPointerException("The call back cannot be null");
if (done)
throw new IllegalStateException(
"Cannot add a call back to a call that has completed.");
callBaks.add(cb);
}
public synchronized Future<Object> callGetMethod(
final String extendedUrlPart, final Map<String, String> params,
final Class<?> returnType) {
UrlHelper hlpr = new UrlHelper(baseUrl, extendedUrlPart, params);
return callGetMethod(hlpr, returnType);
}
public synchronized Future<Object> callGetMethod(
final String fullSecondHalfUrl, final Class<?> returnType) {
UrlHelper hlpr;
try {
hlpr = UrlHelper.parseUrl(baseUrl + "/" + fullSecondHalfUrl);
return callGetMethod(hlpr, returnType);
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
/**
* Method for starting a call to a JsonApi. <br />
* If you want to end this method immediately, you should call the
* Future.get() method. This will block until the results are returned.
*
* @param parameters
* The parameters being passed into the method
* @param returnType
* The type this method will return
* @return A future that will hold the result of the computation
*/
protected synchronized Future<Object> callGetMethod(
final UrlHelper urlHlpr, final Class<?> returnType) {
if (started)
throw new IllegalStateException("Cannot call more than once.");
started = true;
placeParamsInHlpr(urlHlpr, initialParams);
// Submit a task to the the executor.
future = exec.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
String url = urlHlpr.toUrl();
// synchronously make a request in another thread
Object result = client.makeGetRequest(url, returnType);
done = true;
return result;
}
});
// start listening for the future to complete
listenForDone(urlHlpr.getExtendedUrl());
return future;
}
public synchronized Future<Object> callPostMethod(
final String extendedUrlPart, final Map<String, String> params,
final Object inputData, final Class<?> returnType) {
UrlHelper hlpr = new UrlHelper(baseUrl, extendedUrlPart, params);
return callPostMethod(hlpr, inputData, returnType);
}
public synchronized Future<Object> callPostMethod(
final String fullSecondHalfUrl, final Object inputData,
final Class<?> returnType) {
UrlHelper hlpr;
try {
hlpr = UrlHelper.parseUrl(baseUrl + "/" + fullSecondHalfUrl);
return callPostMethod(hlpr, inputData, returnType);
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
/**
* Method for starting a call to a JsonApi.<br />
* If you want to end this method immediately, you should call the
* Future.get() method. This will block until the results are returned.
*
* @param parameters
* The parameters being passed into the method
* @param inputData
* the data to pass into the POST method
* @param returnType
* The type this method will return
* @return A future that will hold the result of the computation
*/
protected synchronized Future<Object> callPostMethod(
final UrlHelper urlHlpr, final Object inputData,
final Class<?> returnType) {
if (started)
throw new IllegalStateException("Cannot call more than once.");
started = true;
placeParamsInHlpr(urlHlpr, initialParams);
// Submit a task to the the executor.
future = exec.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
String url = urlHlpr.toUrl();
// synchronously make a request in another thread
Object result = client.makePostRequest(url, inputData,
returnType);
done = true;
return result;
}
});
// start listening for the future to complete
listenForDone(urlHlpr.getExtendedUrl());
return future;
}
/**
* Waits for the Future to be done, and then executes the CallBacks that
* have been registered to the call.<br />
* If the method throws any exceptions, the handleException method will be
* called instead.
*/
private void listenForDone(final String methodName) {
// New thread for the purpose of listening for a response to complete.
Thread doneListener = new Thread(new Runnable() {
@Override
public void run() {
try {
// This call blocks until the result is calculated
Object result = future.get();
// Pass the result to all of the registered callbacks
for (CallBack cb : callBaks) {
cb.doCallBack(result, baseUrl, methodName);
}
} catch (Exception e) {
// If any exceptions occur during the request the handle
// exception method will be called on every callback.
for (CallBack cb : callBaks) {
cb.handleException(e);
}
}
}
});
// We want the threads to die if all of the main threads exit.
doneListener.setDaemon(true);
doneListener.start();
}
private static void placeParamsInHlpr(UrlHelper hlpr,
Map<String, String> paramsToAdd) {
Map<String, String> result = hlpr.getParams();
for (Entry<String, String> entry : paramsToAdd.entrySet()) {
result.put(entry.getKey(), entry.getValue());
}
hlpr.setParams(result);
}
}