/**
*
*/
package org.commcare.android.tasks.templates;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.UnknownHostException;
import javax.net.ssl.SSLPeerUnverifiedException;
import net.sqlcipher.database.SQLiteDatabase;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.commcare.android.javarosa.AndroidLogger;
import org.commcare.android.util.AndroidStreamUtil;
import org.commcare.android.util.bitcache.BitCache;
import org.commcare.android.util.bitcache.BitCacheFactory;
import org.commcare.dalvik.application.CommCareApplication;
import org.commcare.data.xml.DataModelPullParser;
import org.commcare.data.xml.TransactionParserFactory;
import org.commcare.xml.util.InvalidStructureException;
import org.commcare.xml.util.UnfullfilledRequirementsException;
import org.javarosa.core.services.Logger;
import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
import android.os.AsyncTask;
/**
* @author ctsims
*
*/
public abstract class HttpCalloutTask<R> extends CommCareTask<Void, String, org.commcare.android.tasks.templates.HttpCalloutTask.HttpCalloutOutcomes, R>{
public enum HttpCalloutOutcomes {
NetworkFailure,
BadResponse,
AuthFailed,
UnkownError,
BadCertificate,
Success
}
Context c;
public HttpCalloutTask(Context c) {
this.c = c;
}
protected Context getContext() {
return c;
}
@Override
protected HttpCalloutOutcomes doTaskBackground(Void... params) {
HttpCalloutOutcomes preHttpOutcome = doSetupTaskBeforeRequest();
if(preHttpOutcome != null) {
return preHttpOutcome;
}
//Since we can proceed with the task either way, but we
//still wanna know whether it failed
boolean calloutFailed = false;
if(HttpCalloutNeeded()) {
HttpCalloutOutcomes outcome;
try {
HttpResponse response = doHttpRequest();
int responseCode = response.getStatusLine().getStatusCode();
if(responseCode >= 200 && responseCode < 300) {
outcome = doResponseSuccess(response);
} else if(responseCode == 401) {
outcome = doResponseAuthFailed(response);
} else {
outcome = doResponseOther(response);
}
} catch (ClientProtocolException e) {
//This is a general HTTP exception, basically
outcome = HttpCalloutOutcomes.NetworkFailure;
} catch (UnknownHostException e) {
//HTTP Error
outcome = HttpCalloutOutcomes.NetworkFailure;
} catch(SSLPeerUnverifiedException e){
// Couldn't get a valid SSL certificate
outcome = HttpCalloutOutcomes.BadCertificate;
} catch (IOException e) {
//This is probably related to local files, actually
outcome = HttpCalloutOutcomes.NetworkFailure;
} finally {
}
//If we needed the callout to succeed and it didn't, return our failure.
if(outcome != HttpCalloutOutcomes.Success) {
//TODO:Cleanup?
if(HttpCalloutRequired()) {
return outcome;
} else {
calloutFailed = true;
}
} else {
if(!processSuccesfulRequest()) {
return HttpCalloutOutcomes.BadResponse;
}
}
}
//So either we didn't need our our HTTP callout or we succeeded. Either way, move on
//to the next step
return doPostCalloutTask(calloutFailed);
}
protected boolean processSuccesfulRequest() {
return true;
}
/**
*
* @return
*/
protected HttpCalloutOutcomes doSetupTaskBeforeRequest() {
return null;
}
protected abstract HttpResponse doHttpRequest() throws ClientProtocolException, IOException;
protected HttpCalloutOutcomes doResponseSuccess(HttpResponse response) throws IOException {
beginResponseHandling(response);
InputStream input = cacheResponseOpenHandle(response);
TransactionParserFactory factory = getTransactionParserFactory();
//this is _really_ coupled, but we'll tolerate it for now because of the absurd performance gains
try {
DataModelPullParser parser = new DataModelPullParser(input, factory, true, false);
parser.parse();
return HttpCalloutOutcomes.Success;
//TODO: These are not great, long term
} catch(InvalidStructureException ise) {
ise.printStackTrace();
Logger.log(AndroidLogger.TYPE_USER, "Invalid response for auth keys: " + ise.getMessage());
return HttpCalloutOutcomes.BadResponse;
} catch (XmlPullParserException e) {
e.printStackTrace();
Logger.log(AndroidLogger.TYPE_USER, "Invalid xml response for auth keys: " + e.getMessage());
return HttpCalloutOutcomes.BadResponse;
} catch (UnfullfilledRequirementsException e) {
e.printStackTrace();
Logger.log(AndroidLogger.TYPE_USER, "Missing requirements when fetching auth keys: " + e.getMessage());
return HttpCalloutOutcomes.BadResponse;
} finally {
}
}
protected abstract TransactionParserFactory getTransactionParserFactory();
protected InputStream cacheResponseOpenHandle(HttpResponse response) throws IOException {
int dataSizeGuess = -1;
if(response.containsHeader("Content-Length")) {
String length = response.getFirstHeader("Content-Length").getValue();
try{
dataSizeGuess = Integer.parseInt(length);
} catch(Exception e) {
//Whatever.
}
}
BitCache cache = BitCacheFactory.getCache(c, dataSizeGuess);
cache.initializeCache();
OutputStream cacheOut = cache.getCacheStream();
AndroidStreamUtil.writeFromInputToOutput(response.getEntity().getContent(), cacheOut);
return cache.retrieveCache();
}
protected void beginResponseHandling(HttpResponse response) {
//Nothing unless required
}
protected HttpCalloutOutcomes doResponseAuthFailed(HttpResponse response) {
return HttpCalloutOutcomes.AuthFailed;
}
protected abstract HttpCalloutOutcomes doResponseOther(HttpResponse response);
protected abstract boolean HttpCalloutNeeded();
protected abstract boolean HttpCalloutRequired();
protected abstract HttpCalloutOutcomes doPostCalloutTask(boolean httpFailed);
}