com.benefit.buy.library.http.query.callback.AbstractAjaxCallback.java Source code

Java tutorial

Introduction

Here is the source code for com.benefit.buy.library.http.query.callback.AbstractAjaxCallback.java

Source

/*
 * Copyright 2011 - AndroidQuery.com (tinyeeliu@gmail.com)
 * 
 * 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.benefit.buy.library.http.query.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.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.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.cookie.Cookie;
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 com.benefit.buy.library.http.query.auth.AccountHandle;
import com.benefit.buy.library.http.query.auth.GoogleHandle;
import com.benefit.buy.library.http.query.util.AQUtility;
import com.benefit.buy.library.http.query.util.Common;
import com.benefit.buy.library.http.query.util.Constants;
import com.benefit.buy.library.http.query.util.PredefinedBAOS;
import com.benefit.buy.library.http.query.util.Progress;
import com.benefit.buy.library.http.query.util.XmlDom;

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.text.TextUtils;
import android.util.Log;
import android.util.Xml;
import android.view.View;

/**
 * The core class of ajax callback handler.
 */
public abstract class AbstractAjaxCallback<T, K> 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<T> type;

    private Reference<Object> whandler;

    private Object handler;

    private String callback;

    private WeakReference<Object> progress;

    private String url;

    private String networkUrl;

    protected Map<String, Object> params;

    private Map<String, String> headers;

    private Map<String, String> 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<Activity> 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<T> 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<Object>(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<T> 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<String, String>();
        }
        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<String, String>();
        }
        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<String, Object>();
        }
        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<String, ?> params) {
        this.params = (Map<String, Object>) 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<Object>(progress);
        }
        return self();
    }

    private static final Class<?>[] DEFAULT_SIG = { String.class, Object.class, AjaxStatus.class };

    private boolean completed;

    public 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 = "<meta [^>]*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<Activity>(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<String, Object> extractParams(Uri uri) {
        Map<String, Object> params = new HashMap<String, Object>();
        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<String, Object> 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<String, Object>();
            }
            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<String, String> 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<String, String> 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<String, String> headers, Map<String, Object> 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<String, String> headers, Map<String, Object> 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<String, String> headers,
            Map<String, Object> 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(Constants.POST_ENTITY);
        if (value instanceof HttpEntity) {
            entity = (HttpEntity) value;
        } else {
            List<NameValuePair> pairs = new ArrayList<NameValuePair>();
            for (Map.Entry<String, Object> 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<String, String> 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);
            getCookie(client);
        } 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 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.")) {
            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<String, Object> params) {
        for (Map.Entry<String, Object> 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<String, String> headers, Map<String, Object> 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());
        Object o = null;
        if (progress != null) {
            o = progress.get();
        }
        Progress p = null;
        if (o != null) {
            p = new Progress(o);
        }
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            writeObject(dos, entry.getKey(), entry.getValue(), p);
        }
        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 writeObject(DataOutputStream dos, String name, Object obj, Progress p) throws IOException {
        if (obj == null) {
            return;
        }
        if (obj instanceof File) {
            File file = (File) obj;
            writeData(dos, name, file.getName(), new FileInputStream(file), p);
        } 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 writeData(DataOutputStream dos, String name, String filename, InputStream is, Progress p)
            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);
        AQUtility.copy(is, dos, is.available(), p);
        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);
    }

    public String makeCookie() {
        if ((cookies == null) || (cookies.size() == 0)) {
            return null;
        }
        Iterator<String> 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();
    }

    private void getCookie(DefaultHttpClient httpClient) {
        List<Cookie> cookies = httpClient.getCookieStore().getCookies();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < cookies.size(); i++) {
            Cookie cookie = cookies.get(i);
            String cookieName = cookie.getName();
            String cookieValue = cookie.getValue();
            if (!TextUtils.isEmpty(cookieName) && !TextUtils.isEmpty(cookieValue)) {
                cookie(cookieName, cookieValue);
                sb.append(cookieName + "=");
                sb.append(cookieValue + ";");
            }
        }
        Log.e("cookie", sb.toString());
    }
}