From d0d647c8bde77de4413ac1e6b79d3244929ad542 Mon Sep 17 00:00:00 2001 From: Rafael Munoz Date: Mon, 4 Jun 2012 19:58:14 +0200 Subject: [PATCH 1/2] Allow management of the library HttpClient Make the HttpClient instance used by AbstractAjaxCallback more accesible to the end user allowing him to set his own instance, get the instance and customize it (set a custom RetryHandler, modify the HttpParams, etc) --- .gitattributes | 2 + .gitignore | 4 +- .../callback/AbstractAjaxCallback.java | 47 +++++++++++-------- 3 files changed, 31 insertions(+), 22 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..4be1c918 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Disable LF normalization for all files +* -text \ No newline at end of file diff --git a/.gitignore b/.gitignore index 59125aa8..40e9890c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,5 @@ gen/ # Local configuration file (sdk path, etc) local.properties -proguard-dump.txt -/assets +proguard-dump.txt +/assets diff --git a/src/com/androidquery/callback/AbstractAjaxCallback.java b/src/com/androidquery/callback/AbstractAjaxCallback.java index 6729d392..138ffd89 100644 --- a/src/com/androidquery/callback/AbstractAjaxCallback.java +++ b/src/com/androidquery/callback/AbstractAjaxCallback.java @@ -1111,31 +1111,38 @@ public static void setSSF(SocketFactory sf){ } private static DefaultHttpClient client; - private static DefaultHttpClient getClient(){ + public static DefaultHttpClient getClient(){ - if(client == null){ - - HttpParams httpParams = new BasicHttpParams(); - HttpConnectionParams.setConnectionTimeout(httpParams, NET_TIMEOUT); - HttpConnectionParams.setSoTimeout(httpParams, NET_TIMEOUT); - - ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(NETWORK_POOL)); - - - //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); - + if(client == null){ + client = initDefaultClient(); } return client; } + public static void setClient(DefaultHttpClient paramClient){ + client = paramClient; + } + + public static DefaultHttpClient initDefaultClient(){ + HttpParams httpParams = new BasicHttpParams(); + HttpConnectionParams.setConnectionTimeout(httpParams, NET_TIMEOUT); + HttpConnectionParams.setSoTimeout(httpParams, NET_TIMEOUT); + + ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(NETWORK_POOL)); + + //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; + } + private void httpDo(HttpUriRequest hr, String url, Map headers, AjaxStatus status) throws ClientProtocolException, IOException{ From 95ae999431a99d2175e9864034bfc26fafd6f9ea Mon Sep 17 00:00:00 2001 From: Rafael Munoz Date: Fri, 8 Jun 2012 20:01:00 +0200 Subject: [PATCH 2/2] Added MultipartParameter Added MultipartParameter to allow the user to set filenames and ContentTypes --- .../callback/AbstractAjaxCallback.java | 1612 +++++++++-------- 1 file changed, 851 insertions(+), 761 deletions(-) diff --git a/src/com/androidquery/callback/AbstractAjaxCallback.java b/src/com/androidquery/callback/AbstractAjaxCallback.java index 138ffd89..292a06a3 100644 --- a/src/com/androidquery/callback/AbstractAjaxCallback.java +++ b/src/com/androidquery/callback/AbstractAjaxCallback.java @@ -22,10 +22,12 @@ import java.io.FileInputStream; 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.net.URLConnection; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -35,6 +37,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; import org.apache.http.Header; import org.apache.http.HttpEntity; @@ -55,15 +58,18 @@ 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.entity.HttpEntityWrapper; 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.BasicHeader; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.BasicHttpParams; 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.HTTP; import org.apache.http.protocol.HttpContext; import org.json.JSONArray; import org.json.JSONObject; @@ -89,102 +95,107 @@ /** * The core class of ajax callback handler. - * + * */ -public abstract class AbstractAjaxCallback implements Runnable{ - +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 Class type; private Reference whandler; private Object handler; private String callback; private WeakReference progress; - + private String url; private Map params; private Map headers; private Map cookies; - + private Transformer transformer; - + protected T result; - + private File cacheDir; private AccountHandle ah; - + protected AjaxStatus status; - + protected boolean fileCache; protected boolean memCache; private boolean refresh; - + private long expire; private String encoding = "UTF-8"; private WeakReference act; - + private boolean uiCallback = true; - + @SuppressWarnings("unchecked") - private K self(){ + private K self() { return (K) this; } - - private void clear(){ + + private void clear() { whandler = null; handler = null; progress = null; } - + /** * Sets the timeout. - * - * @param timeout the default network timeout in milliseconds + * + * @param timeout + * the default network timeout in milliseconds */ - public static void setTimeout(int timeout){ + public static void setTimeout(int timeout) { NET_TIMEOUT = timeout; } - + /** * Sets the agent. - * - * @param agent the default agent sent in http header + * + * @param agent + * the default agent sent in http header */ - public static void setAgent(String agent){ + public static void setAgent(String agent) { AGENT = agent; } - + /** * Use gzip. - * + * * @param gzip */ - public static void setGZip(boolean 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(). + * 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 + * 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){ + + public static void setTransformer(Transformer transformer) { st = transformer; } - - + /** * Gets the ajax response type. - * + * * @return the type */ public Class getType() { @@ -192,163 +203,187 @@ public Class getType() { } /** - * 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 + * 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){ + 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 + * 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){ + public K handler(Object handler, String callback) { this.handler = handler; this.callback = callback; this.whandler = null; return self(); } - + /** * Url. - * - * @param url the url + * + * @param url + * the url * @return self */ - public K url(String url){ + public K url(String url) { this.url = url; return self(); } - + /** - * Set the desired ajax response type. Type parameter is required otherwise the ajax callback will not occur. + * 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 * - * Current supported type: JSONObject.class, String.class, byte[].class, Bitmap.class, XmlDom.class * - * - * @param type the type + * @param type + * the type * @return self */ - public K type(Class type){ + public K type(Class type) { this.type = type; return self(); } - - + /** - * Set the transformer that transform raw data to desired type. - * If not set, default transformer will be used. + * 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. + * JSONObject, JSONArray, XmlDom, String, byte[], and Bitmap. + * * - * - * @param transformer transformer + * @param transformer + * transformer * @return self */ - public K transformer(Transformer transformer){ + public K transformer(Transformer transformer) { this.transformer = transformer; return self(); } - + /** * Set ajax request to be file cached. - * - * @param cache the cache + * + * @param cache + * the cache * @return self */ - public K fileCache(boolean cache){ + 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 + * 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){ + public K memCache(boolean cache) { this.memCache = cache; return self(); } - + /** * Indicate the ajax request should ignore memcache and filecache. - * - * @param refresh the refresh + * + * @param refresh + * the refresh * @return self */ - public K refresh(boolean refresh){ + 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 + * 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){ + 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 + * 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){ + 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 + * + * @param name + * the name + * @param value + * the value * @return self */ - public K header(String name, String value){ - if(headers == null){ + 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 + * + * @param name + * the name + * @param value + * the value * @return self */ - public K cookie(String name, String value){ - if(cookies == null){ + 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. * @@ -357,1134 +392,1189 @@ public K cookie(String name, String value){ * @param encoding * @return self */ - public K encoding(String encoding){ + public K encoding(String encoding) { this.encoding = encoding; 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. + * 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. * - * 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 + * @param name + * the name + * @param value + * the value * @return self */ - public K param(String name, Object value){ - if(params == null){ + 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 + * + * @param params + * the params * @return self */ - + @SuppressWarnings("unchecked") - public K params(Map params){ + 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 + * 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){ + 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){ + public K progress(Dialog dialog) { return progress((Object) dialog); } - - public K progress(Object progress){ - if(progress != null){ + + 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 static final Class[] DEFAULT_SIG = { String.class, Object.class, AjaxStatus.class }; + private boolean completed; - void callback(){ - + + void callback() { + showProgress(false); - + completed = true; - - if(isActive()){ - - if(callback != null){ + + if (isActive()) { + + if (callback != null) { Object handler = getHandler(); - Class[] AJAX_SIG = {String.class, type, AjaxStatus.class}; - AQUtility.invokeHandler(handler, callback, true, false, AJAX_SIG, DEFAULT_SIG, url, result, status); - }else{ + Class[] AJAX_SIG = { String.class, type, AjaxStatus.class }; + AQUtility.invokeHandler(handler, callback, true, false, AJAX_SIG, DEFAULT_SIG, url, result, status); + } else { callback(url, result, status); } - + } - + filePut(); - + wake(); AQUtility.debugNotify(); } - - private void wake(){ - - if(!blocked) return; - - synchronized(this){ - try{ + + private void wake() { + + if (!blocked) + return; + + synchronized (this) { + try { notifyAll(); - }catch(Exception e){ + } 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. - * + * 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()){ + + public void block() { + + if (AQUtility.isUIThread()) { throw new IllegalStateException("Cannot block UI thread."); } - - if(completed) return; - - try{ - synchronized(this){ + + 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 + // 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){ + } catch (Exception e) { } - + } - - + /** * The callback method to be overwritten for subclasses. - * - * @param url the url - * @param object the object - * @param status the status + * + * @param url + * the url + * @param object + * the object + * @param status + * the status */ - public void callback(String url, T object, AjaxStatus status){ - + public void callback(String url, T object, AjaxStatus status) { + } - - protected T fileGet(String url, File file, AjaxStatus status){ - try { - byte[] data = AQUtility.toBytes(new FileInputStream(file)); + + protected T fileGet(String url, File file, AjaxStatus status) { + try { + byte[] data = AQUtility.toBytes(new FileInputStream(file)); return transform(url, data, status); - } catch(Exception e) { + } catch (Exception e) { AQUtility.debug(e); return null; } } - - protected T datastoreGet(String url){ - + + protected T datastoreGet(String url) { + return null; - + } - - protected void showProgress(boolean show){ - - if(progress != null){ - + + protected void showProgress(boolean show) { + + if (progress != null) { + Common.showProgress(progress.get(), url, show); /* - Object p = progress.get(); - - if(p instanceof View){ - - View pv = (View) p; - - if(show){ - pv.setTag(AQuery.TAG_URL, url); - pv.setVisibility(View.VISIBLE); - }else{ - Object tag = pv.getTag(AQuery.TAG_URL); - if(tag == null || tag.equals(url)){ - pv.setTag(AQuery.TAG_URL, null); - pv.setVisibility(View.GONE); - } - } - }else if(p instanceof Dialog){ - - Dialog pd = (Dialog) p; - - if(show){ - pd.show(); - }else{ - pd.hide(); - } - - }*/ + * Object p = progress.get(); + * + * if(p instanceof View){ + * + * View pv = (View) p; + * + * if(show){ pv.setTag(AQuery.TAG_URL, url); + * pv.setVisibility(View.VISIBLE); }else{ Object tag = + * pv.getTag(AQuery.TAG_URL); if(tag == null || tag.equals(url)){ + * pv.setTag(AQuery.TAG_URL, null); pv.setVisibility(View.GONE); } } + * }else if(p instanceof Dialog){ + * + * Dialog pd = (Dialog) p; + * + * if(show){ pd.show(); }else{ pd.hide(); } + * + * } + */ } - + } - + @SuppressWarnings("unchecked") - protected T transform(String url, byte[] data, AjaxStatus status){ - - if(data == null || type == null){ + protected T transform(String url, byte[] data, AjaxStatus status) { + + if (data == null || type == null) { return null; } - - if(type.equals(JSONObject.class)){ - + + if (type.equals(JSONObject.class)) { + JSONObject result = null; String str = null; - try { - str = new String(data, encoding); + try { + str = new String(data, encoding); result = (JSONObject) new JSONTokener(str).nextValue(); - } catch (Exception e) { + } catch (Exception e) { AQUtility.debug(e); AQUtility.debug(str); } return (T) result; } - - if(type.equals(JSONArray.class)){ - + + if (type.equals(JSONArray.class)) { + JSONArray result = null; - - try { - String str = new String(data, encoding); + + try { + String str = new String(data, encoding); result = (JSONArray) new JSONTokener(str).nextValue(); - } catch (Exception e) { + } catch (Exception e) { AQUtility.debug(e); } return (T) result; } - - if(type.equals(String.class)){ + + if (type.equals(String.class)) { String result = null; - - try { - result = new String(data, encoding); - } catch (Exception e) { + + try { + result = new String(data, encoding); + } catch (Exception e) { AQUtility.debug(e); } return (T) result; } - - if(type.equals(XmlDom.class)){ - + + if (type.equals(XmlDom.class)) { + XmlDom result = null; - - try { + + try { result = new XmlDom(data); - } catch (Exception e) { + } catch (Exception e) { AQUtility.debug(e); } - - return (T) result; + + return (T) result; } - - - if(type.equals(byte[].class)){ + + if (type.equals(byte[].class)) { return (T) data; } - - if(type.equals(Bitmap.class)){ + + if (type.equals(Bitmap.class)) { return (T) BitmapFactory.decodeByteArray(data, 0, data.length); } - - - if(type.equals(XmlPullParser.class)){ + + if (type.equals(XmlPullParser.class)) { XmlPullParser parser = Xml.newPullParser(); - try{ + try { parser.setInput(new ByteArrayInputStream(data), encoding); - }catch(Exception e) { + } catch (Exception e) { AQUtility.report(e); return null; } return (T) parser; } - - if(transformer != null){ + + if (transformer != null) { return transformer.transform(url, type, encoding, data, status); } - - if(st != null){ + + if (st != null) { return st.transform(url, type, encoding, data, status); } - + return null; } - - - - protected T memGet(String url){ + protected T memGet(String url) { return null; } - - - protected void memPut(String url, T object){ + + protected void memPut(String url, T object) { } - - protected void filePut(String url, T object, File file, byte[] data){ - - if(file == null || data == null) return; - + + 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; - + + 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){ + + 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 + * 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){ - + public void async(Activity act) { + this.act = new WeakReference(act); async((Context) act); - + } - - - + /** - * Starts the async process. - * - * @param context the context + * Starts the async process. + * + * @param context + * the context */ - public void async(Context context){ - - if(status == null){ + public void async(Context context) { + + if (status == null) { status = new AjaxStatus(); status.redirect(url).refresh(refresh); } - + showProgress(true); - - if(ah != null){ - - if(!ah.authenticated()){ + + 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()){ + + 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){ + + public void failure(int code, String message) { + + if (status != null) { status.code(code).message(message); callback(); } - + } - - //protected void execute(){ - - //ExecutorService exe = getExecutor(); - //exe.execute(this); - - //} - - private void work(Context context){ - + + // protected void execute(){ + + // ExecutorService exe = getExecutor(); + // exe.execute(this); + + // } + + private void work(Context context) { + T object = memGet(url); - - if(object != null){ + + if (object != null) { result = object; status.source(AjaxStatus.MEMORY).done(); callback(); - }else{ - - if(fileCache) cacheDir = AQUtility.getCacheDir(context); - //execute(); + } else { + + if (fileCache) + cacheDir = AQUtility.getCacheDir(context); + // execute(); execute(this); } } - - protected boolean cacheAvailable(Context context){ + + protected boolean cacheAvailable(Context context) { return fileCache && AQUtility.getExistedCacheByUrl(context, url) != null; } - - + /** * AQuert internal use. Do not call this method directly. */ - + @Override public void run() { - - - if(!status.getDone()){ - - try{ - backgroundWork(); - }catch(Throwable e){ + + 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){ + + if (!status.getReauth()) { + // if doesn't need to reauth + if (uiCallback) { AQUtility.post(this); - }else{ + } else { afterWork(); } } - }else{ + } else { afterWork(); } - - - - + } - - private void backgroundWork(){ - - - - if(!refresh){ - - if(fileCache){ - fileWork(); + + private void backgroundWork() { + + if (!refresh) { + + if (fileCache) { + fileWork(); } } - - if(result == null){ - datastoreWork(); + + if (result == null) { + datastoreWork(); } - - if(result == null){ + + if (result == null) { networkWork(); } - - + } - - private String getCacheUrl(){ - if(ah != null){ + + private String getCacheUrl() { + if (ah != null) { return ah.getCacheUrl(url); } return url; } - - private void fileWork(){ - + + private void fileWork() { + File file = accessFile(cacheDir, getCacheUrl()); - - //if file exist - if(file != null){ - //convert + + // if file exist + if (file != null) { + // convert result = fileGet(url, file, status); - //if result is ok - if(result != null){ + // if result is ok + if (result != null) { status.source(AjaxStatus.FILE).time(new Date(file.lastModified())).done(); } } } - - private void datastoreWork(){ - + + private void datastoreWork() { + result = datastoreGet(url); - - if(result != null){ + + if (result != null) { status.source(AjaxStatus.DATASTORE).done(); } } - + private boolean reauth; - private void networkWork(){ - - if(url == null){ + + private void networkWork() { + + if (url == null) { status.code(AjaxStatus.NETWORK_ERROR).done(); return; } - - + byte[] data = null; - - try{ - + + try { + network(); - - if(ah != null && ah.expired(this, status) && !reauth){ - AQUtility.debug("reauth needed", status.getMessage()); + + if (ah != null && ah.expired(this, status) && !reauth) { + AQUtility.debug("reauth needed", status.getMessage()); reauth = true; - if(ah.reauth(this)){ + if (ah.reauth(this)) { network(); - }else{ - status.reauth(true); + } else { + status.reauth(true); return; } } - + data = status.getData(); - - }catch(Exception e){ + + } catch (Exception e) { AQUtility.debug(e); status.code(AjaxStatus.NETWORK_ERROR).message("network error"); } - - - try{ + + try { result = transform(url, data, status); - }catch(Exception e){ + } catch (Exception e) { AQUtility.debug(e); } - - if(result == null && data != null){ - status.code(AjaxStatus.TRANSFORM_ERROR).message("transform error"); + + if (result == null && data != null) { + status.code(AjaxStatus.TRANSFORM_ERROR).message("transform error"); } - + lastStatus = status.getCode(); status.done(); } - - protected File getCacheFile(){ + + protected File getCacheFile() { return AQUtility.getCacheFile(cacheDir, getCacheUrl()); } - - - private void filePut(){ - - if(result != null && fileCache){ - + + private void filePut() { + + if (result != null && fileCache) { + byte[] data = status.getData(); - - try{ - if(data != null && status.getSource() == AjaxStatus.NETWORK){ - + + try { + if (data != null && status.getSource() == AjaxStatus.NETWORK) { + File file = getCacheFile(); - if(!status.getInvalid()){ - //AQUtility.debug("write", url); + if (!status.getInvalid()) { + // AQUtility.debug("write", url); filePut(url, result, file, data); - }else{ - if(file.exists()){ + } else { + if (file.exists()) { file.delete(); } } - + } - }catch(Exception e){ + } catch (Exception e) { AQUtility.debug(e); } - + status.data(null); } } - - private static String extractUrl(Uri uri){ - + + private static String extractUrl(Uri uri) { + String result = uri.getScheme() + "://" + uri.getAuthority() + uri.getPath(); - + String fragment = uri.getFragment(); - if(fragment != null) result += "#" + fragment; - + if (fragment != null) + result += "#" + fragment; + return result; } - - private static Map extractParams(Uri uri){ - - Map params = new HashMap(); + + private static Map extractParams(Uri uri) { + + Map params = new HashMap(); String[] pairs = uri.getQuery().split("&"); - - for(String pair: pairs){ + + for (String pair : pairs) { String[] split = pair.split("="); - if(split.length >= 2){ + if (split.length >= 2) { params.put(split[0], split[1]); - }else if(split.length == 1){ + } else if (split.length == 1) { params.put(split[0], ""); } } return params; } - - - private void network() throws IOException{ - - + + 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){ + + // 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); } - - if(ah != null){ + + if (ah != null) { url = ah.getNetworkUrl(url); } - - if(params == null){ - httpGet(url, headers, status); - }else{ - if(isMultiPart(params)){ + + if (params == null) { + httpGet(url, headers, status); + } else { + if (isMultiPart(params)) { httpMulti(url, headers, params, status); - }else{ + } else { httpPost(url, headers, params, status); } - + } - + } - - - private void afterWork(){ - - if(url != null && memCache){ + + 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); + + public static void execute(Runnable job) { + + if (fetchExe == null) { + fetchExe = Executors.newFixedThreadPool(NETWORK_POOL); } - + fetchExe.execute(job); } - + /** * Sets the simultaneous network threads limit. Highest limit is 25. - * - * @param limit the new network threads limit + * + * @param limit + * the new network threads limit */ - public static void setNetworkLimit(int limit){ - + public static void setNetworkLimit(int limit) { + NETWORK_POOL = Math.max(1, Math.min(25, limit)); fetchExe = null; } - + /** * Cancel ALL ajax tasks. */ - - public static void cancel(){ - - if(fetchExe != null){ + + public static void cancel() { + + if (fetchExe != null) { fetchExe.shutdownNow(); fetchExe = null; } - + BitmapAjaxCallback.clearTasks(); } - - private static String patchUrl(String url){ - + + 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{ - + + 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 httpPost(String url, Map headers, Map params, AjaxStatus status) throws ClientProtocolException, IOException{ - + + private void httpPost(String url, Map headers, Map params, AjaxStatus status) throws ClientProtocolException, IOException { + AQUtility.debug("post", url); - - + HttpPost post = new HttpPost(url); - + HttpEntity entity = null; - + Object value = params.get(AQuery.POST_ENTITY); - - if(value instanceof HttpEntity){ - entity = (HttpEntity) value; - }else{ - + + if (value instanceof HttpEntity) { + entity = (HttpEntity) value; + } else { + List pairs = new ArrayList(); - - for(Map.Entry e: params.entrySet()){ + + for (Map.Entry e : params.entrySet()) { value = e.getValue(); - if(value != null){ - pairs.add(new BasicNameValuePair(e.getKey(), value.toString())); + if (value != null) { + pairs.add(new BasicNameValuePair(e.getKey(), value.toString())); } } - + entity = new UrlEncodedFormEntity(pairs, "UTF-8"); - + } - - - if(headers != null && !headers.containsKey("Content-Type")){ + + if (headers != null && !headers.containsKey("Content-Type")) { headers.put("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); } - + post.setEntity(entity); httpDo(post, url, headers, status); - - + } - + private static SocketFactory ssf; - public static void setSSF(SocketFactory sf){ + + public static void setSSF(SocketFactory sf) { ssf = sf; client = null; } - + private static DefaultHttpClient client; - public static DefaultHttpClient getClient(){ - - if(client == null){ + + public static DefaultHttpClient getClient() { + + if (client == null) { client = initDefaultClient(); } return client; } - - public static void setClient(DefaultHttpClient paramClient){ + + public static void setClient(DefaultHttpClient paramClient) { client = paramClient; } - - public static DefaultHttpClient initDefaultClient(){ + + public static DefaultHttpClient initDefaultClient() { HttpParams httpParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParams, NET_TIMEOUT); HttpConnectionParams.setSoTimeout(httpParams, NET_TIMEOUT); - + ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(NETWORK_POOL)); - - //Added this line to avoid issue at: http://stackoverflow.com/questions/5358014/android-httpclient-oom-on-4g-lte-htc-thunderbolt + + // 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); + + ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParams, registry); client = new DefaultHttpClient(cm, httpParams); - + return client; } - - - private void httpDo(HttpUriRequest hr, String url, Map headers, AjaxStatus status) throws ClientProtocolException, IOException{ - - if(AGENT != null){ + + 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")){ + + 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){ + if (cookie != null) { hr.addHeader("Cookie", cookie); } - - if(ah != null){ + + if (ah != null) { ah.applyToken(this, hr); } - + DefaultHttpClient client = getClient(); - - HttpContext context = new BasicHttpContext(); + + HttpContext context = new BasicHttpContext(); CookieStore cookieStore = new BasicCookieStore(); context.setAttribute(ClientContext.COOKIE_STORE, cookieStore); - - + HttpResponse response = client.execute(hr, context); - - byte[] data = null; - - String redirect = url; - - int code = response.getStatusLine().getStatusCode(); - String message = response.getStatusLine().getReasonPhrase(); - String error = null; - - - if(code < 200 || code >= 300){ - - try{ - HttpEntity entity = response.getEntity(); - byte[] s = AQUtility.toBytes(entity.getContent()); - error = new String(s, "UTF-8"); - AQUtility.debug("error", error); - }catch(Exception e){ - AQUtility.debug(e); - } - - - }else{ - - HttpEntity entity = response.getEntity(); - + + byte[] data = null; + + String redirect = url; + + int code = response.getStatusLine().getStatusCode(); + String message = response.getStatusLine().getReasonPhrase(); + String error = null; + + if (code < 200 || code >= 300) { + + try { + HttpEntity entity = response.getEntity(); + byte[] s = AQUtility.toBytes(entity.getContent()); + error = new String(s, "UTF-8"); + AQUtility.debug("error", error); + } catch (Exception e) { + AQUtility.debug(e); + } + + } else { + + HttpEntity entity = response.getEntity(); + 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())); - - PredefinedBAOS baos = new PredefinedBAOS(size); - - Header encoding = entity.getContentEncoding(); - if(encoding != null && encoding.getValue().equalsIgnoreCase("gzip")) { - InputStream is = new GZIPInputStream(entity.getContent()); - AQUtility.copy(is, baos); - }else{ - entity.writeTo(baos); - } - - - data = baos.toByteArray(); - } - - 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).client(client).context(context).headers(response.getAllHeaders()); - - + redirect = currentHost.toURI() + currentReq.getURI(); + + int size = Math.max(32, Math.min(1024 * 64, (int) entity.getContentLength())); + + PredefinedBAOS baos = new PredefinedBAOS(size); + + Header encoding = entity.getContentEncoding(); + if (encoding != null && encoding.getValue().equalsIgnoreCase("gzip")) { + InputStream is = new GZIPInputStream(entity.getContent()); + AQUtility.copy(is, baos); + } else { + entity.writeTo(baos); + } + + data = baos.toByteArray(); + } + + 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).client(client).context(context) + .headers(response.getAllHeaders()); + } - + /** * 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 someone@gmail.com + * + * @param act + * the current activity + * @param type + * the auth type + * @param account + * the account, such as someone@gmail.com * @return self */ - public K auth(Activity act, String type, String account){ - - if(android.os.Build.VERSION.SDK_INT >= 5 && type.startsWith("g.")){ + 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 + * + * @param handle + * the account handle * @return self */ - - public K auth(AccountHandle handle){ + + public K auth(AccountHandle handle) { ah = handle; return self(); } - + /** * Gets the url. - * + * * @return the url */ - public String getUrl(){ + public String getUrl() { return url; } - + /** * Gets the handler. - * + * * @return the handler */ public Object getHandler() { - if(handler != null) return handler; - if(whandler == null) return null; + 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(){ + + 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. - * + * 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(){ + public T getResult() { return result; } - + /** - * Gets the ajax status. - * This method should only be used after the block() method. - * + * Gets the ajax status. This method should only be used after the block() + * method. + * * @return the status */ - - public AjaxStatus getStatus(){ + + public AjaxStatus getStatus() { return status; } - + /** * Gets the encoding. Default is UTF-8. - * + * * @return the encoding */ - public String getEncoding(){ + public String getEncoding() { return encoding; } - + 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()){ + + 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[]) return true; + if (value instanceof File || value instanceof byte[] || value instanceof MultipartParameter) + 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)); - } - } - + if (headers != null) { + for (String name : headers.keySet()) { + conn.setRequestProperty(name, headers.get(name)); + } + } + String cookie = makeCookie(); - if(cookie != null){ + if (cookie != null) { conn.setRequestProperty("Cookie", cookie); } - + dos = new DataOutputStream(conn.getOutputStream()); - for(Map.Entry entry: params.entrySet()){ - + 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; - - if(code < 200 || code >= 300){ - //throw new IOException(); - }else{ - - InputStream is = conn.getInputStream(); - data = AQUtility.toBytes(is); - - } - - AQUtility.debug("response", code); - if(data != null){ - AQUtility.debug(data.length, url); - } - - status.code(code).message(message).redirect(url).time(new Date()).data(data).client(null); - - - + + int code = conn.getResponseCode(); + String message = conn.getResponseMessage(); + + byte[] data = null; + + if (code < 200 || code >= 300) { + // throw new IOException(); + } else { + + InputStream is = conn.getInputStream(); + data = AQUtility.toBytes(is); + + } + + AQUtility.debug("response", code); + if (data != null) { + AQUtility.debug(data.length, url); + } + + status.code(code).message(message).redirect(url).time(new Date()).data(data).client(null); + } - - private static void writeObject(DataOutputStream dos, String name, Object obj) throws IOException{ + + private static void writeObject(DataOutputStream dos, String name, Object obj) throws IOException { + + if (obj == null) + return; + + String filename = name; + String contentType = null; + + if (obj instanceof MultipartParameter) { + MultipartParameter parameter = (MultipartParameter) obj; + obj = parameter.getObject(); + if(parameter.getFilename() != null) filename = parameter.getFilename(); + contentType = parameter.getContentType(); + if(contentType == null) contentType = URLConnection.getFileNameMap().getContentTypeFor(filename); + } - if(obj == null) return; - if(obj instanceof File){ - writeData(dos, name, new FileInputStream((File) obj)); - }else if(obj instanceof byte[]){ - writeData(dos, name, new ByteArrayInputStream((byte[]) obj)); - }else{ + if (obj instanceof File) { + writeData(dos, name, filename, contentType, new FileInputStream((File) obj)); + } else if (obj instanceof byte[]) { + writeData(dos, name, filename, contentType, new ByteArrayInputStream((byte[]) obj)); + } else { writeField(dos, name, obj.toString()); } - + } - - - private static void writeData(DataOutputStream dos, String name, InputStream is) throws IOException { - + + private static void writeData(DataOutputStream dos, String name, String filename, String contentType, InputStream is) throws IOException { + dos.writeBytes(twoHyphens + boundary + lineEnd); - dos.writeBytes("Content-Disposition: form-data; name=\""+name+"\";" - + " filename=\"" + name + "\"" + lineEnd); + + dos.writeBytes("Content-Disposition: form-data; name=\"" + name + "\";" + " filename=\"" + filename + "\"" + lineEnd); + + if(contentType != null && contentType.trim().length()!=0) { + dos.writeBytes("Content-Type: " + contentType + 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; - + + private String makeCookie() { + + if (cookies == null || cookies.size() == 0) + return null; + Iterator iter = cookies.keySet().iterator(); - + StringBuilder sb = new StringBuilder(); - - while(iter.hasNext()){ + + while (iter.hasNext()) { String key = iter.next(); String value = cookies.get(key); sb.append(key); sb.append("="); sb.append(value); - if(iter.hasNext()){ + if (iter.hasNext()) { sb.append("; "); } } - + return sb.toString(); - + } -} + public static class MultipartParameter { + + private byte[] bytes; + private File file; + private String filename; + private String contentType; + + + private MultipartParameter(byte[] bytes) { + this.bytes = bytes; + } + + private MultipartParameter(File file) { + this.file = file; + } + + + public MultipartParameter build(byte[] bytes) { + return new MultipartParameter(bytes); + } + + public MultipartParameter build(File file) { + return new MultipartParameter(file); + } + + public byte[] getBytes() { + return bytes; + } + + public File getFile() { + return file; + } + public Object getObject() { + return bytes != null ? bytes : file; + } + + public String getFilename() { + return filename; + } + public MultipartParameter filename(String filename) { + this.filename = filename; + return this; + } + + public String getContentType() { + return contentType; + } + public MultipartParameter contentType(String contentType) { + this.contentType = contentType; + return this; + } + + + } + +}