Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.androidquery.callback.AbstractAjaxCallback Maven / Gradle / Ivy
Go to download
Android-Query (AQuery) is a light-weight library for doing asynchronous tasks and manipulating UI elements in Android
/*
* Copyright 2011 - AndroidQuery.com ([email protected] )
*
* 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.androidquery.callback;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.xmlpull.v1.XmlPullParser;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Xml;
import android.view.View;
import com.androidquery.AQuery;
import com.androidquery.auth.AccountHandle;
import com.androidquery.auth.GoogleHandle;
import com.androidquery.util.AQUtility;
import com.androidquery.util.Common;
import com.androidquery.util.Constants;
import com.androidquery.util.PredefinedBAOS;
import com.androidquery.util.Progress;
import com.androidquery.util.XmlDom;
/**
* The core class of ajax callback handler.
*
*/
public abstract class AbstractAjaxCallback implements Runnable{
private static int NET_TIMEOUT = 30000;
private static String AGENT = null;
private static int NETWORK_POOL = 4;
private static boolean GZIP = true;
private static boolean REUSE_CLIENT = true;
private Class type;
private Reference whandler;
private Object handler;
private String callback;
private WeakReference progress;
private String url;
private String networkUrl;
private Map params;
private Map headers;
private Map cookies;
private Transformer transformer;
protected T result;
private int policy = Constants.CACHE_DEFAULT;
private File cacheDir;
private File targetFile;
private AccountHandle ah;
protected AjaxStatus status;
protected boolean fileCache;
protected boolean memCache;
private boolean refresh;
private int timeout = 0;
private long expire;
private String encoding = "UTF-8";
private WeakReference act;
private int method = Constants.METHOD_DETECT;
private HttpUriRequest request;
private boolean uiCallback = true;
private int retry = 0;
@SuppressWarnings("unchecked")
private K self(){
return (K) this;
}
private void clear(){
whandler = null;
handler = null;
progress = null;
request = null;
transformer = null;
ah = null;
act = null;
}
/**
* Sets the timeout.
*
* @param timeout the default network timeout in milliseconds
*/
public static void setTimeout(int timeout){
NET_TIMEOUT = timeout;
}
/**
* Sets the agent.
*
* @param agent the default agent sent in http header
*/
public static void setAgent(String agent){
AGENT = agent;
}
/**
* Use gzip.
*
* @param gzip
*/
public static void setGZip(boolean gzip){
GZIP = gzip;
}
/**
* Sets the default static transformer. This transformer should be stateless.
* If state is required, use the AjaxCallback.transformer() or AQuery.transformer().
*
* Transformers are selected in the following priority:
* 1. Native 2. instance transformer() 3. static setTransformer()
*
* @param agent the default transformer to transform raw data to specified type
*/
private static Transformer st;
public static void setTransformer(Transformer transformer){
st = transformer;
}
/**
* Gets the ajax response type.
*
* @return the type
*/
public Class getType() {
return type;
}
/**
* Set a callback handler with a weak reference. Use weak handler if you do not want the ajax callback to hold the handler object from garbage collection.
* For example, if the handler is an activity, weakHandler should be used since the method shouldn't be invoked if an activity is already dead and garbage collected.
*
* @param handler the handler
* @param callback the callback
* @return self
*/
public K weakHandler(Object handler, String callback){
this.whandler = new WeakReference(handler);
this.callback = callback;
this.handler = null;
return self();
}
/**
* Set a callback handler. See weakHandler for handler objects, such as Activity, that should not be held from garbaged collected.
*
* @param handler the handler
* @param callback the callback
* @return self
*/
public K handler(Object handler, String callback){
this.handler = handler;
this.callback = callback;
this.whandler = null;
return self();
}
/**
* Url.
*
* @param url the url
* @return self
*/
public K url(String url){
this.url = url;
return self();
}
public K networkUrl(String url){
this.networkUrl = url;
return self();
}
/**
* Set the desired ajax response type. Type parameter is required otherwise the ajax callback will not occur.
*
* Current supported type: JSONObject.class, String.class, byte[].class, Bitmap.class, XmlDom.class
*
*
* @param type the type
* @return self
*/
public K type(Class type){
this.type = type;
return self();
}
public K method(int method){
this.method = method;
return self();
}
public K timeout(int timeout){
this.timeout = timeout;
return self();
}
public K retry(int retry){
this.retry = retry;
return self();
}
/**
* Set the transformer that transform raw data to desired type.
* If not set, default transformer will be used.
*
* Default transformer supports:
*
* JSONObject, JSONArray, XmlDom, String, byte[], and Bitmap.
*
*
* @param transformer transformer
* @return self
*/
public K transformer(Transformer transformer){
this.transformer = transformer;
return self();
}
/**
* Set ajax request to be file cached.
*
* @param cache the cache
* @return self
*/
public K fileCache(boolean cache){
this.fileCache = cache;
return self();
}
/**
* Indicate ajax request to be memcached. Note: The default ajax handler does not supply a memcache.
* Subclasses such as BitmapAjaxCallback can provide their own memcache.
*
* @param cache the cache
* @return self
*/
public K memCache(boolean cache){
this.memCache = cache;
return self();
}
public K policy(int policy){
this.policy = policy;
return self();
}
/**
* Indicate the ajax request should ignore memcache and filecache.
*
* @param refresh the refresh
* @return self
*/
public K refresh(boolean refresh){
this.refresh = refresh;
return self();
}
/**
* Indicate the ajax request should use the main ui thread for callback. Default is true.
*
* @param uiCallback use the main ui thread for callback
* @return self
*/
public K uiCallback(boolean uiCallback){
this.uiCallback = uiCallback;
return self();
}
/**
* The expire duation for filecache. If a cached copy will be served if a cached file exists within current time minus expire duration.
*
* @param expire the expire
* @return self
*/
public K expire(long expire){
this.expire = expire;
return self();
}
/**
* Set the header fields for the http request.
*
* @param name the name
* @param value the value
* @return self
*/
public K header(String name, String value){
if(headers == null){
headers = new HashMap();
}
headers.put(name, value);
return self();
}
/**
* Set the cookies for the http request.
*
* @param name the name
* @param value the value
* @return self
*/
public K cookie(String name, String value){
if(cookies == null){
cookies = new HashMap();
}
cookies.put(name, value);
return self();
}
/**
* Set the encoding used to parse the response.
*
* Default is UTF-8.
*
* @param encoding
* @return self
*/
public K encoding(String encoding){
this.encoding = encoding;
return self();
}
private HttpHost proxy;
public K proxy(String host, int port){
proxy = new HttpHost(host, port);
return self();
}
public K targetFile(File file){
this.targetFile = file;
return self();
}
/**
* Set http POST params. If params are set, http POST method will be used.
* The UTF-8 encoded value.toString() will be sent with POST.
*
* Header field "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" will be added if no Content-Type header field presents.
*
* @param name the name
* @param value the value
* @return self
*/
public K param(String name, Object value){
if(params == null){
params = new HashMap();
}
params.put(name, value);
return self();
}
/**
* Set the http POST params. See param(String name, Object value).
*
* @param params the params
* @return self
*/
@SuppressWarnings("unchecked")
public K params(Map params){
this.params = (Map) params;
return self();
}
/**
* Set the progress view (can be a progress bar or any view) to be shown (VISIBLE) and hide (GONE) depends on progress.
*
* @param view the progress view
* @return self
*/
public K progress(View view){
return progress((Object) view);
}
/**
* Set the dialog to be shown and dismissed depends on progress.
*
* @param dialog
* @return self
*/
public K progress(Dialog dialog){
return progress((Object) dialog);
}
public K progress(Object progress){
if(progress != null){
this.progress = new WeakReference(progress);
}
return self();
}
private static final Class>[] DEFAULT_SIG = {String.class, Object.class, AjaxStatus.class};
private boolean completed;
void callback(){
showProgress(false);
completed = true;
if(isActive()){
if(callback != null){
Object handler = getHandler();
Class>[] AJAX_SIG = {String.class, type, AjaxStatus.class};
AQUtility.invokeHandler(handler, callback, true, true, AJAX_SIG, DEFAULT_SIG, url, result, status);
}else{
try{
callback(url, result, status);
}catch(Exception e){
AQUtility.report(e);
}
}
}else{
skip(url, result, status);
}
filePut();
if(!blocked){
status.close();
}
wake();
AQUtility.debugNotify();
}
private void wake(){
if(!blocked) return;
synchronized(this){
try{
notifyAll();
}catch(Exception e){
}
}
}
private boolean blocked;
/**
* Block the current thread until the ajax call is completed. Returns immediately if ajax is already completed.
* Exception will be thrown if this method is called in main thread.
*
*/
public void block(){
if(AQUtility.isUIThread()){
throw new IllegalStateException("Cannot block UI thread.");
}
if(completed) return;
try{
synchronized(this){
blocked = true;
//wait at most the network timeout plus 5 seconds, this guarantee thread will never be blocked forever
this.wait(NET_TIMEOUT + 5000);
}
}catch(Exception e){
}
}
/**
* The callback method to be overwritten for subclasses.
*
* @param url the url
* @param object the object
* @param status the status
*/
public void callback(String url, T object, AjaxStatus status){
}
protected void skip(String url, T object, AjaxStatus status){
}
protected T fileGet(String url, File file, AjaxStatus status){
try {
byte[] data = null;
if(isStreamingContent()){
status.file(file);
}else{
data = AQUtility.toBytes(new FileInputStream(file));
}
return transform(url, data, status);
} catch(Exception e) {
AQUtility.debug(e);
return null;
}
}
protected T datastoreGet(String url){
return null;
}
protected void showProgress(final boolean show){
final Object p = progress == null ? null : progress.get();
if(p != null){
if(AQUtility.isUIThread()){
Common.showProgress(p, url, show);
}else{
AQUtility.post(new Runnable() {
@Override
public void run() {
Common.showProgress(p, url, show);
}
});
}
}
}
@SuppressWarnings("unchecked")
protected T transform(String url, byte[] data, AjaxStatus status){
if(type == null){
return null;
}
File file = status.getFile();
if(data != null){
if(type.equals(Bitmap.class)){
return (T) BitmapFactory.decodeByteArray(data, 0, data.length);
}
if(type.equals(JSONObject.class)){
JSONObject result = null;
String str = null;
try {
str = new String(data, encoding);
result = (JSONObject) new JSONTokener(str).nextValue();
} catch (Exception e) {
AQUtility.debug(e);
AQUtility.debug(str);
}
return (T) result;
}
if(type.equals(JSONArray.class)){
JSONArray result = null;
try {
String str = new String(data, encoding);
result = (JSONArray) new JSONTokener(str).nextValue();
} catch (Exception e) {
AQUtility.debug(e);
}
return (T) result;
}
if(type.equals(String.class)){
String result = null;
if(status.getSource() == AjaxStatus.NETWORK){
AQUtility.debug("network");
result = correctEncoding(data, encoding, status);
}else{
AQUtility.debug("file");
try {
result = new String(data, encoding);
} catch (Exception e) {
AQUtility.debug(e);
}
}
return (T) result;
}
/*
if(type.equals(XmlDom.class)){
XmlDom result = null;
try {
result = new XmlDom(data);
} catch (Exception e) {
AQUtility.debug(e);
}
return (T) result;
}
*/
if(type.equals(byte[].class)){
return (T) data;
}
if(transformer != null){
return transformer.transform(url, type, encoding, data, status);
}
if(st != null){
return st.transform(url, type, encoding, data, status);
}
}else if(file != null){
if(type.equals(File.class)){
return (T) file;
}
if(type.equals(XmlDom.class)){
XmlDom result = null;
try {
FileInputStream fis = new FileInputStream(file);
result = new XmlDom(fis);
status.closeLater(fis);
} catch (Exception e) {
AQUtility.report(e);
return null;
}
return (T) result;
}
if(type.equals(XmlPullParser.class)){
XmlPullParser parser = Xml.newPullParser();
try{
FileInputStream fis = new FileInputStream(file);
parser.setInput(fis, encoding);
status.closeLater(fis);
}catch(Exception e) {
AQUtility.report(e);
return null;
}
return (T) parser;
}
if(type.equals(InputStream.class)){
try{
FileInputStream fis = new FileInputStream(file);
status.closeLater(fis);
return (T) fis;
}catch(Exception e) {
AQUtility.report(e);
return null;
}
}
}
return null;
}
//This is an adhoc way to get charset without html parsing library, might not cover all cases.
private String getCharset(String html){
String pattern = " ]*http-equiv[^>]*\"Content-Type\"[^>]*>";
Pattern p = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(html);
if(!m.find()) return null;
String tag = m.group();
return parseCharset(tag);
}
private String parseCharset(String tag){
if(tag == null) return null;
int i = tag.indexOf("charset");
if(i == -1) return null;
int e = tag.indexOf(";", i) ;
if(e == -1) e = tag.length();
String charset = tag.substring(i + 7, e).replaceAll("[^\\w-]", "");
return charset;
}
private String correctEncoding(byte[] data, String target, AjaxStatus status){
String result = null;
try{
if(!"utf-8".equalsIgnoreCase(target)){
return new String(data, target);
}
String header = parseCharset(status.getHeader("Content-Type"));
AQUtility.debug("parsing header", header);
if(header != null){
return new String(data, header);
}
result = new String(data, "utf-8");
String charset = getCharset(result);
AQUtility.debug("parsing needed", charset);
if(charset != null && !"utf-8".equalsIgnoreCase(charset)){
AQUtility.debug("correction needed", charset);
result = new String(data, charset);
status.data(result.getBytes("utf-8"));
}
}catch(Exception e){
AQUtility.report(e);
}
return result;
}
protected T memGet(String url){
return null;
}
protected void memPut(String url, T object){
}
protected void filePut(String url, T object, File file, byte[] data){
if(file == null || data == null) return;
AQUtility.storeAsync(file, data, 0);
}
protected File accessFile(File cacheDir, String url){
if(expire < 0) return null;
File file = AQUtility.getExistedCacheByUrl(cacheDir, url);
if(file != null && expire != 0){
long diff = System.currentTimeMillis() - file.lastModified();
if(diff > expire){
return null;
}
}
return file;
}
/**
* Starts the async process.
*
* If activity is passed, the callback method will not be invoked if the activity is no longer in use.
* Specifically, isFinishing() is called to determine if the activity is active.
*
* @param act activity
*/
public void async(Activity act){
if(act.isFinishing()){
AQUtility.warn("Warning", "Possible memory leak. Calling ajax with a terminated activity.");
}
if(type == null){
AQUtility.warn("Warning", "type() is not called with response type.");
return;
}
this.act = new WeakReference(act);
async((Context) act);
}
/**
* Starts the async process.
*
* @param context the context
*/
public void async(Context context){
if(status == null){
status = new AjaxStatus();
status.redirect(url).refresh(refresh);
}else if(status.getDone()){
status.reset();
result = null;
}
showProgress(true);
if(ah != null){
if(!ah.authenticated()){
AQUtility.debug("auth needed", url);
ah.auth(this);
return;
}
}
work(context);
}
private boolean isActive(){
if(act == null) return true;
Activity a = act.get();
if(a == null || a.isFinishing()){
return false;
}
return true;
}
public void failure(int code, String message){
if(status != null){
status.code(code).message(message);
callback();
}
}
private void work(Context context){
T object = memGet(url);
if(object != null){
result = object;
status.source(AjaxStatus.MEMORY).done();
callback();
}else{
cacheDir = AQUtility.getCacheDir(context, policy);
execute(this);
}
}
protected boolean cacheAvailable(Context context){
//return fileCache && AQUtility.getExistedCacheByUrl(context, url) != null;
return fileCache && AQUtility.getExistedCacheByUrl(AQUtility.getCacheDir(context, policy), url) != null;
}
/**
* AQuert internal use. Do not call this method directly.
*/
@Override
public void run() {
if(!status.getDone()){
try{
backgroundWork();
}catch(Throwable e){
AQUtility.debug(e);
status.code(AjaxStatus.NETWORK_ERROR).done();
}
if(!status.getReauth()){
//if doesn't need to reauth
if(uiCallback){
AQUtility.post(this);
}else{
afterWork();
}
}
}else{
afterWork();
}
}
private void backgroundWork(){
if(!refresh){
if(fileCache){
fileWork();
}
}
if(result == null){
datastoreWork();
}
if(result == null){
networkWork();
}
}
private String getCacheUrl(){
if(ah != null){
return ah.getCacheUrl(url);
}
return url;
}
private String getNetworkUrl(String url){
String result = url;
if(networkUrl != null){
result = networkUrl;
}
if(ah != null){
result = ah.getNetworkUrl(result);
}
return result;
}
private void fileWork(){
File file = accessFile(cacheDir, getCacheUrl());
//if file exist
if(file != null){
//convert
status.source(AjaxStatus.FILE);
result = fileGet(url, file, status);
//if result is ok
if(result != null){
status.time(new Date(file.lastModified())).done();
}
}
}
private void datastoreWork(){
result = datastoreGet(url);
if(result != null){
status.source(AjaxStatus.DATASTORE).done();
}
}
private boolean reauth;
private void networkWork(){
if(url == null){
status.code(AjaxStatus.NETWORK_ERROR).done();
return;
}
byte[] data = null;
try{
network(retry + 1);
if(ah != null && ah.expired(this, status) && !reauth){
AQUtility.debug("reauth needed", status.getMessage());
reauth = true;
if(ah.reauth(this)){
network();
}else{
status.reauth(true);
return;
}
}
data = status.getData();
}catch(Exception e){
AQUtility.debug(e);
status.code(AjaxStatus.NETWORK_ERROR).message("network error");
}
try{
result = transform(url, data, status);
}catch(Exception e){
AQUtility.debug(e);
}
if(result == null && data != null){
status.code(AjaxStatus.TRANSFORM_ERROR).message("transform error");
}
lastStatus = status.getCode();
status.done();
}
protected File getCacheFile(){
return AQUtility.getCacheFile(cacheDir, getCacheUrl());
}
protected boolean isStreamingContent(){
return File.class.equals(type) || XmlPullParser.class.equals(type) || InputStream.class.equals(type) || XmlDom.class.equals(type);
}
private File getPreFile(){
boolean pre = isStreamingContent();
File result = null;
if(pre){
if(targetFile != null){
result = targetFile;
}else if(fileCache){
result = getCacheFile();
}else{
File dir = AQUtility.getTempDir();
if(dir == null) dir = cacheDir;
result = AQUtility.getCacheFile(dir, url);
}
}
if(result != null && !result.exists()){
try{
result.getParentFile().mkdirs();
result.createNewFile();
}catch(Exception e){
AQUtility.report(e);
return null;
}
}
return result;
}
private void filePut(){
if(result != null && fileCache){
byte[] data = status.getData();
try{
if(data != null && status.getSource() == AjaxStatus.NETWORK){
File file = getCacheFile();
if(!status.getInvalid()){
//AQUtility.debug("write", url);
filePut(url, result, file, data);
}else{
if(file.exists()){
file.delete();
}
}
}
}catch(Exception e){
AQUtility.debug(e);
}
status.data(null);
}
}
private static String extractUrl(Uri uri){
String result = uri.getScheme() + "://" + uri.getAuthority() + uri.getPath();
String fragment = uri.getFragment();
if(fragment != null) result += "#" + fragment;
return result;
}
private static Map extractParams(Uri uri){
Map params = new HashMap();
String[] pairs = uri.getQuery().split("&");
for(String pair: pairs){
String[] split = pair.split("=");
if(split.length >= 2){
params.put(split[0], split[1]);
}else if(split.length == 1){
params.put(split[0], "");
}
}
return params;
}
//added retry logic
private void network(int attempts) throws IOException{
if(attempts <= 1){
network();
return;
}
for(int i = 0; i < attempts; i++){
try{
network();
return;
}catch(IOException e){
if(i == attempts - 1){
throw e;
}
}
}
}
private void network() throws IOException{
String url = this.url;
Map params = this.params;
//convert get to post request, if url length is too long to be handled on web
if(params == null && url.length() > 2000){
Uri uri = Uri.parse(url);
url = extractUrl(uri);
params = extractParams(uri);
}
url = getNetworkUrl(url);
if(Constants.METHOD_DELETE == method){
httpDelete(url, headers, status);
}else if(Constants.METHOD_PUT == method){
httpPut(url, headers, params, status);
}else{
if(Constants.METHOD_POST == method && params == null){
params = new HashMap();
}
if(params == null){
httpGet(url, headers, status);
}else{
if(isMultiPart(params)){
httpMulti(url, headers, params, status);
}else{
httpPost(url, headers, params, status);
}
}
}
}
private void afterWork(){
if(url != null && memCache){
memPut(url, result);
}
callback();
clear();
}
private static ExecutorService fetchExe;
public static void execute(Runnable job){
if(fetchExe == null){
fetchExe = Executors.newFixedThreadPool(NETWORK_POOL);
}
fetchExe.execute(job);
}
/**
* Return the number of active ajax threads. Note that this doesn't necessarily correspond to active network connections.
* Ajax threads might be reading a cached url from file system or transforming the response after a network transfer.
*
*/
public static int getActiveCount(){
int result = 0;
if(fetchExe instanceof ThreadPoolExecutor){
result = ((ThreadPoolExecutor) fetchExe).getActiveCount();
}
return result;
}
/**
* Sets the simultaneous network threads limit. Highest limit is 25.
*
* @param limit the new network threads limit
*/
public static void setNetworkLimit(int limit){
NETWORK_POOL = Math.max(1, Math.min(25, limit));
fetchExe = null;
AQUtility.debug("setting network limit", NETWORK_POOL);
}
/**
* Cancel ALL ajax tasks.
*
* Warning: Do not call this method unless you are exiting an application.
*
*/
public static void cancel(){
if(fetchExe != null){
fetchExe.shutdownNow();
fetchExe = null;
}
BitmapAjaxCallback.clearTasks();
}
private static String patchUrl(String url){
url = url.replaceAll(" ", "%20").replaceAll("\\|", "%7C");
return url;
}
private void httpGet(String url, Map headers, AjaxStatus status) throws IOException{
AQUtility.debug("get", url);
url = patchUrl(url);
HttpGet get = new HttpGet(url);
httpDo(get, url, headers, status);
}
private void httpDelete(String url, Map headers, AjaxStatus status) throws IOException{
AQUtility.debug("get", url);
url = patchUrl(url);
HttpDelete del = new HttpDelete(url);
httpDo(del, url, headers, status);
}
private void httpPost(String url, Map headers, Map params, AjaxStatus status) throws ClientProtocolException, IOException{
AQUtility.debug("post", url);
HttpEntityEnclosingRequestBase req = new HttpPost(url);
httpEntity(url, req, headers, params, status);
}
private void httpPut(String url, Map headers, Map params, AjaxStatus status) throws ClientProtocolException, IOException{
AQUtility.debug("put", url);
HttpEntityEnclosingRequestBase req = new HttpPut(url);
httpEntity(url, req, headers, params, status);
}
private void httpEntity(String url, HttpEntityEnclosingRequestBase req, Map headers, Map params, AjaxStatus status) throws ClientProtocolException, IOException{
//This setting seems to improve post performance
//http://stackoverflow.com/questions/3046424/http-post-requests-using-httpclient-take-2-seconds-why
req.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
HttpEntity entity = null;
Object value = params.get(AQuery.POST_ENTITY);
if(value instanceof HttpEntity){
entity = (HttpEntity) value;
}else{
List pairs = new ArrayList();
for(Map.Entry e: params.entrySet()){
value = e.getValue();
if(value != null){
pairs.add(new BasicNameValuePair(e.getKey(), value.toString()));
}
}
entity = new UrlEncodedFormEntity(pairs, "UTF-8");
}
if(headers != null && !headers.containsKey("Content-Type")){
headers.put("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
}
req.setEntity(entity);
httpDo(req, url, headers, status);
}
private static SocketFactory ssf;
/**
* Set the secure socket factory.
*
* Could be used to work around SSL certificate not truested issue.
*
* http://stackoverflow.com/questions/1217141/self-signed-ssl-acceptance-android
*/
public static void setSSF(SocketFactory sf){
ssf = sf;
client = null;
}
public static void setReuseHttpClient(boolean reuse){
REUSE_CLIENT = reuse;
client = null;
}
private static DefaultHttpClient client;
private static DefaultHttpClient getClient(){
if(client == null || !REUSE_CLIENT){
AQUtility.debug("creating http client");
HttpParams httpParams = new BasicHttpParams();
//httpParams.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
HttpConnectionParams.setConnectionTimeout(httpParams, NET_TIMEOUT);
HttpConnectionParams.setSoTimeout(httpParams, NET_TIMEOUT);
//ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(NETWORK_POOL));
ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(25));
//Added this line to avoid issue at: http://stackoverflow.com/questions/5358014/android-httpclient-oom-on-4g-lte-htc-thunderbolt
HttpConnectionParams.setSocketBufferSize(httpParams, 8192);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", ssf == null ? SSLSocketFactory.getSocketFactory() : ssf, 443));
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, registry);
client = new DefaultHttpClient(cm, httpParams);
}
return client;
}
//helper method to support underscore subdomain
private HttpResponse execute(HttpUriRequest hr, DefaultHttpClient client, HttpContext context) throws ClientProtocolException, IOException{
HttpResponse response = null;
if(hr.getURI().getAuthority().contains("_")) {
URL urlObj = hr.getURI().toURL();
HttpHost host;
if(urlObj.getPort() == -1) {
host = new HttpHost(urlObj.getHost(), 80, urlObj.getProtocol());
} else {
host = new HttpHost(urlObj.getHost(), urlObj.getPort(), urlObj.getProtocol());
}
response = client.execute(host, hr, context);
} else {
response = client.execute(hr, context);
}
return response;
}
private void httpDo(HttpUriRequest hr, String url, Map headers, AjaxStatus status) throws ClientProtocolException, IOException{
if(AGENT != null){
hr.addHeader("User-Agent", AGENT);
}
if(headers != null){
for(String name: headers.keySet()){
hr.addHeader(name, headers.get(name));
}
}
if(GZIP && (headers == null || !headers.containsKey("Accept-Encoding"))){
hr.addHeader("Accept-Encoding", "gzip");
}
String cookie = makeCookie();
if(cookie != null){
hr.addHeader("Cookie", cookie);
}
if(ah != null){
ah.applyToken(this, hr);
}
DefaultHttpClient client = getClient();
HttpParams hp = hr.getParams();
if(proxy != null) hp.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
if(timeout > 0){
hp.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
hp.setParameter(CoreConnectionPNames.SO_TIMEOUT, timeout);
}
HttpContext context = new BasicHttpContext();
CookieStore cookieStore = new BasicCookieStore();
context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
request = hr;
if(abort){
throw new IOException("Aborted");
}
HttpResponse response = null;
try{
//response = client.execute(hr, context);
response = execute(hr, client, context);
}catch(HttpHostConnectException e){
//if proxy is used, automatically retry without proxy
if(proxy != null){
AQUtility.debug("proxy failed, retrying without proxy");
hp.setParameter(ConnRoutePNames.DEFAULT_PROXY, null);
//response = client.execute(hr, context);
response = execute(hr, client, context);
}else{
throw e;
}
}
byte[] data = null;
String redirect = url;
int code = response.getStatusLine().getStatusCode();
String message = response.getStatusLine().getReasonPhrase();
String error = null;
HttpEntity entity = response.getEntity();
File file = null;
if(code < 200 || code >= 300){
InputStream is = null;
try{
if(entity != null){
is = entity.getContent();
byte[] s = toData(getEncoding(entity), is);
error = new String(s, "UTF-8");
AQUtility.debug("error", error);
}
}catch(Exception e){
AQUtility.debug(e);
}finally{
AQUtility.close(is);
}
}else{
HttpHost currentHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
HttpUriRequest currentReq = (HttpUriRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);
redirect = currentHost.toURI() + currentReq.getURI();
int size = Math.max(32, Math.min(1024 * 64, (int) entity.getContentLength()));
OutputStream os = null;
InputStream is = null;
try{
file = getPreFile();
if(file == null){
os = new PredefinedBAOS(size);
}else{
file.createNewFile();
os = new BufferedOutputStream(new FileOutputStream(file));
}
is = entity.getContent();
if("gzip".equalsIgnoreCase(getEncoding(entity))){
is = new GZIPInputStream(is);
}
copy(is, os, (int) entity.getContentLength());
os.flush();
if(file == null){
data = ((PredefinedBAOS) os).toByteArray();
}else{
if(!file.exists() || file.length() == 0){
file = null;
}
}
}finally{
AQUtility.close(is);
AQUtility.close(os);
}
}
AQUtility.debug("response", code);
if(data != null){
AQUtility.debug(data.length, url);
}
status.code(code).message(message).error(error).redirect(redirect).time(new Date()).data(data).file(file).client(client).context(context).headers(response.getAllHeaders());
}
private String getEncoding(HttpEntity entity){
if(entity == null) return null;
Header eheader = entity.getContentEncoding();
if(eheader == null) return null;
return eheader.getValue();
}
private void copy(InputStream is, OutputStream os, int max) throws IOException{
Object o = null;
if(progress != null){
o = progress.get();
}
Progress p = null;
if(o != null){
p = new Progress(o);
}
AQUtility.copy(is, os, max, p);
}
/*
private void copy(InputStream is, OutputStream os, String encoding, int max) throws IOException{
if("gzip".equalsIgnoreCase(encoding)){
is = new GZIPInputStream(is);
}
Object o = null;
if(progress != null){
o = progress.get();
}
Progress p = null;
if(o != null){
p = new Progress(o);
}
AQUtility.copy(is, os, max, p);
}
*/
/**
* Set the authentication type of this request. This method requires API 5+.
*
* @param act the current activity
* @param type the auth type
* @param account the account, such as [email protected]
* @return self
*/
public K auth(Activity act, String type, String account){
if(android.os.Build.VERSION.SDK_INT >= 5 && type.startsWith("g.")){
ah = new GoogleHandle(act, type, account);
}
return self();
}
/**
* Set the authentication account handle.
*
* @param handle the account handle
* @return self
*/
public K auth(AccountHandle handle){
ah = handle;
return self();
}
/**
* Gets the url.
*
* @return the url
*/
public String getUrl(){
return url;
}
/**
* Gets the handler.
*
* @return the handler
*/
public Object getHandler() {
if(handler != null) return handler;
if(whandler == null) return null;
return whandler.get();
}
/**
* Gets the callback method name.
*
* @return the callback
*/
public String getCallback() {
return callback;
}
private static int lastStatus = 200;
protected static int getLastStatus(){
return lastStatus;
}
/**
* Gets the result. Can be null if ajax is not completed or the ajax call failed.
* This method should only be used after the block() method.
*
* @return the result
*/
public T getResult(){
return result;
}
/**
* Gets the ajax status.
* This method should only be used after the block() method.
*
* @return the status
*/
public AjaxStatus getStatus(){
return status;
}
/**
* Gets the encoding. Default is UTF-8.
*
* @return the encoding
*/
public String getEncoding(){
return encoding;
}
private boolean abort;
/**
* Abort the http request that will interrupt the network transfer.
* This method currently doesn't work with multi-part post.
*
* If no network transfer is involved (eg. response is file cached), this method has no effect.
*
*/
public void abort(){
abort = true;
if(request != null && !request.isAborted()){
request.abort();
}
}
private static final String lineEnd = "\r\n";
private static final String twoHyphens = "--";
private static final String boundary = "*****";
private static boolean isMultiPart(Map params){
for(Map.Entry entry: params.entrySet()){
Object value = entry.getValue();
AQUtility.debug(entry.getKey(), value);
if(value instanceof File || value instanceof byte[] || value instanceof InputStream) return true;
}
return false;
}
private void httpMulti(String url, Map headers, Map params, AjaxStatus status) throws IOException {
AQUtility.debug("multipart", url);
HttpURLConnection conn = null;
DataOutputStream dos = null;
URL u = new URL(url);
conn = (HttpURLConnection) u.openConnection();
conn.setInstanceFollowRedirects(false);
conn.setConnectTimeout(NET_TIMEOUT * 4);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "multipart/form-data;charset=utf-8;boundary=" + boundary);
if(headers != null){
for(String name: headers.keySet()){
conn.setRequestProperty(name, headers.get(name));
}
}
String cookie = makeCookie();
if(cookie != null){
conn.setRequestProperty("Cookie", cookie);
}
if(ah != null){
ah.applyToken(this, conn);
}
dos = new DataOutputStream(conn.getOutputStream());
for(Map.Entry entry: params.entrySet()){
writeObject(dos, entry.getKey(), entry.getValue());
}
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
dos.flush();
dos.close();
conn.connect();
int code = conn.getResponseCode();
String message = conn.getResponseMessage();
byte[] data = null;
String encoding = conn.getContentEncoding();
String error = null;
if(code < 200 || code >= 300){
error = new String(toData(encoding, conn.getErrorStream()), "UTF-8");
AQUtility.debug("error", error);
}else{
data = toData(encoding, conn.getInputStream());
}
AQUtility.debug("response", code);
if(data != null){
AQUtility.debug(data.length, url);
}
status.code(code).message(message).redirect(url).time(new Date()).data(data).error(error).client(null);
}
private byte[] toData(String encoding, InputStream is) throws IOException{
boolean gzip = "gzip".equalsIgnoreCase(encoding);
if(gzip){
is = new GZIPInputStream(is);
}
return AQUtility.toBytes(is);
}
private static void writeObject(DataOutputStream dos, String name, Object obj) throws IOException{
if(obj == null) return;
if(obj instanceof File){
File file = (File) obj;
writeData(dos, name, file.getName(), new FileInputStream(file));
}else if(obj instanceof byte[]){
writeData(dos, name, name, new ByteArrayInputStream((byte[]) obj));
}else if(obj instanceof InputStream){
writeData(dos, name, name, (InputStream) obj);
}else{
writeField(dos, name, obj.toString());
}
}
private static void writeData(DataOutputStream dos, String name, String filename, InputStream is) throws IOException {
dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\""+name+"\";"
+ " filename=\"" + filename + "\"" + lineEnd);
//added to specify type
dos.writeBytes("Content-Type: application/octet-stream");
dos.writeBytes(lineEnd);
dos.writeBytes("Content-Transfer-Encoding: binary");
dos.writeBytes(lineEnd);
dos.writeBytes(lineEnd);
AQUtility.copy(is, dos);
dos.writeBytes(lineEnd);
}
private static void writeField(DataOutputStream dos, String name, String value) throws IOException {
dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"");
dos.writeBytes(lineEnd);
dos.writeBytes(lineEnd);
byte[] data = value.getBytes("UTF-8");
dos.write(data);
dos.writeBytes(lineEnd);
}
private String makeCookie(){
if(cookies == null || cookies.size() == 0) return null;
Iterator iter = cookies.keySet().iterator();
StringBuilder sb = new StringBuilder();
while(iter.hasNext()){
String key = iter.next();
String value = cookies.get(key);
sb.append(key);
sb.append("=");
sb.append(value);
if(iter.hasNext()){
sb.append("; ");
}
}
return sb.toString();
}
}