me.code4fun.roboq.Request.java Source code

Java tutorial

Introduction

Here is the source code for me.code4fun.roboq.Request.java

Source

/*
 * ====================================================================
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

package me.code4fun.roboq;

import android.util.Log;
import me.code4fun.roboq.multipart.HttpMultipartMode;
import me.code4fun.roboq.multipart.MultipartEntity;
import me.code4fun.roboq.multipart.content.ByteArrayBody;
import me.code4fun.roboq.multipart.content.FileBody;
import me.code4fun.roboq.multipart.content.InputStreamBody;
import me.code4fun.roboq.multipart.content.StringBody;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.RedirectHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.DefaultRedirectHandler;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.protocol.HttpContext;

import java.io.*;
import java.lang.reflect.Array;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;

/**
 * Request
 *
 * @since 0.1
 */
public class Request extends RequestBase<Request> {

    public static final String LOG_TAG = "Roboq.Request";

    public static final int GET = 1;
    public static final int POST = 2;
    public static final int PUT = 3;
    public static final int DELETE = 4;
    public static final int TRACE = 5;
    public static final int HEAD = 6;
    public static final int OPTIONS = 7;

    private static final String DEFAULT_BINARY_CONTENT_TYPE = "application/octet-stream";
    private static final String DEFAULT_TEXT_CONTENT_TYPE = "text/plain";

    protected final Prepared prepared;
    protected int method;
    protected Object tag;

    public Request(int method, String url) {
        this(null, method, url, (Options) null);
    }

    public Request(int method, String url, Object... options) {
        this(null, method, url, options);
    }

    public Request(int method, String url, Options options) {
        this(null, method, url, options);
    }

    public Request(Prepared prepared, int method, String url) {
        this(prepared, method, url, (Options) null);
    }

    public Request(Prepared prepared, int method, String url, Object... options) {
        this(prepared, method, url, new Options(options));
    }

    public Request(Prepared prepared, int method, String url, Options options) {
        this.prepared = prepared;
        setMethod(method);
        setUrl(url);
        with(options);
    }

    public Prepared getPrepared() {
        return prepared;
    }

    public int getMethod() {
        return method;
    }

    public Request setMethod(int method) {
        this.method = method;
        return this;
    }

    public String getMethodAsText() {
        return getMethodAsText(this.method);
    }

    public Object getTag() {
        return tag;
    }

    public Request setTag(Object tag) {
        this.tag = tag;
        return this;
    }

    protected int mergeMethod() {
        int method1 = method;
        if (prepared != null && prepared.modifier != null)
            method1 = prepared.modifier.modifyMethod(method1);
        if (modifier != null)
            method1 = modifier.modifyMethod(method1);

        return method1;
    }

    protected String mergeUrl() {
        String url1;
        if (url != null) {
            if (url.startsWith("http://") || url.startsWith("https://")) {
                url1 = url;
            } else {
                if (prepared != null && prepared.url != null)
                    url1 = prepared.url + url;
                else
                    url1 = url;
            }
        } else {
            url1 = prepared != null && prepared.url != null ? prepared.url : null;
        }

        if (prepared != null && prepared.modifier != null)
            url1 = prepared.modifier.modifyUrl(url1);
        if (modifier != null)
            url1 = modifier.modifyUrl(url1);

        return url1;
    }

    protected Options mergeOptions() {
        Options options1 = new Options();
        if (prepared != null) {
            options1.putAll(prepared.options);
        }
        options1.putAll(options);

        if (prepared != null && prepared.modifier != null)
            options1 = prepared.modifier.modifyOptions(options1);
        if (modifier != null)
            options1 = modifier.modifyOptions(options1);

        if (options1 == null)
            options1 = new Options();

        return options1;
    }

    protected static <T> T selectValue(T val, T preparedVal, T def) {
        if (val != null)
            return val;

        if (preparedVal != null)
            return preparedVal;

        return def;
    }

    protected Response createResponse(HttpResponse httpResp) {
        return new Response(httpResp);
    }

    private String makeUrl(String url, Options opts) {
        String url1 = url;

        // expand path vars
        for (Map.Entry<String, Object> entry : opts.getPathVars().entrySet()) {
            String k = entry.getKey();
            url1 = url1.replace("${" + k + "}", o2s(entry.getValue(), ""));
        }

        // params
        ArrayList<NameValuePair> nvl = new ArrayList<NameValuePair>();
        for (Map.Entry<String, Object> entry : opts.getParams().entrySet()) {
            nvl.add(new BasicNameValuePair(entry.getKey(), o2s(entry.getValue(), "")));
        }
        if (nvl.isEmpty()) {
            return url1;
        } else {
            return url1 + "?" + URLEncodedUtils.format(nvl,
                    selectValue(paramsEncoding, prepared != null ? prepared.paramsEncoding : null, "UTF-8"));
        }
    }

    protected HttpUriRequest createHttpRequest(int method, String url, Options opts) {
        String url1 = makeUrl(url, opts);

        // Create HTTP request
        HttpUriRequest httpReq;
        if (GET == method) {
            httpReq = new HttpGet(url1);
        } else if (POST == method) {
            httpReq = new HttpPost(url1);
        } else if (PUT == method) {
            httpReq = new HttpPut(url1);
        } else if (DELETE == method) {
            httpReq = new HttpDelete(url1);
        } else if (TRACE == method) {
            httpReq = new HttpTrace(url1);
        } else if (HEAD == method) {
            httpReq = new HttpHead(url1);
        } else if (OPTIONS == method) {
            httpReq = new HttpOptions(url1);
        } else {
            throw new IllegalStateException("Illegal HTTP method " + method);
        }

        // Set headers
        Map<String, Object> headers = opts.getHeaders();
        for (Map.Entry<String, Object> entry : headers.entrySet()) {
            String k = entry.getKey();
            Object v = entry.getValue();
            if (v != null && v.getClass().isArray()) {
                int len = Array.getLength(v);
                for (int i = 0; i < len; i++) {
                    Object v0 = Array.get(v, i);
                    httpReq.addHeader(k, o2s(v0, ""));
                }
            } else if (v instanceof List) {
                for (Object v0 : (List) v)
                    httpReq.addHeader(k, o2s(v0, ""));
            } else {
                httpReq.addHeader(k, o2s(v, ""));
            }
        }

        // set body
        if (httpReq instanceof HttpEntityEnclosingRequestBase) {
            ((HttpEntityEnclosingRequestBase) httpReq).setEntity(createRequestBody(method, url, opts));
        }

        return httpReq;
    }

    protected HttpClient createHttpClient(int method, String url, Options opts) {
        DefaultHttpClient httpClient = new DefaultHttpClient();

        // retry count
        int retryCount1 = selectValue(retryCount, prepared != null ? prepared.retryCount : null, 1);
        if (retryCount1 <= 0)
            retryCount1 = 1;
        httpClient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(retryCount1, false));

        // redirect
        boolean redirect1 = selectValue(redirect, prepared != null ? prepared.redirect : null, true);
        if (redirect1) {
            httpClient.setRedirectHandler(new DefaultRedirectHandler());
        } else {
            httpClient.setRedirectHandler(new RedirectHandler() {
                @Override
                public boolean isRedirectRequested(HttpResponse httpResponse, HttpContext httpContext) {
                    return false;
                }

                @Override
                public URI getLocationURI(HttpResponse httpResponse, HttpContext httpContext) {
                    return null;
                }
            });
        }

        // 
        Integer connTimeout1 = selectValue(connectionTimeout, prepared != null ? prepared.connectionTimeout : null,
                20000);
        httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, connTimeout1);

        // Socket
        Integer soTimeout1 = selectValue(soTimeout, prepared != null ? prepared.soTimeout : null, 0);
        httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, soTimeout1);

        return httpClient;
    }

    protected String o2s(Object obj, String def) {
        return obj != null ? obj.toString() : def;
    }

    protected String getFileContentType(File f) {
        // TODO: ???content-type
        return DEFAULT_BINARY_CONTENT_TYPE;
    }

    protected HttpEntity createRequestBody(int method, String url, Options opts) {
        Object bodyObj = opts.getBody();
        if (bodyObj != null) {
            return createBody(bodyObj);
        } else {
            return createFieldsBody(opts.getFields());
        }
    }

    protected OutputStream decorateBodyOutput(OutputStream out) {
        return out;
    }

    private HttpEntity createBody(Object o) {
        Object o1 = o instanceof Body ? ((Body) o).content : o;
        String c1 = o instanceof Body ? ((Body) o).contentType : null;

        if (o1 instanceof byte[]) {
            ByteArrayEntity entity = new ByteArrayEntity((byte[]) o1) {
                @Override
                public void writeTo(OutputStream outstream) throws IOException {
                    super.writeTo(decorateBodyOutput(outstream));
                }
            };
            entity.setContentType(c1 != null ? c1 : DEFAULT_BINARY_CONTENT_TYPE);
            return entity;
        } else if (o1 instanceof InputStream) {
            Long len = o instanceof Body ? ((Body) o).length : null;
            if (len == null)
                throw new RoboqException("Missing length in body for upload InputStream");

            InputStreamEntity entity = new InputStreamEntity((InputStream) o1, len) {
                @Override
                public void writeTo(OutputStream outstream) throws IOException {
                    super.writeTo(decorateBodyOutput(outstream));
                }
            };
            entity.setContentType(c1 != null ? c1 : DEFAULT_BINARY_CONTENT_TYPE);
            return entity;
        } else if (o1 instanceof File) {
            File f = (File) o1;
            return new FileEntity(f, c1 != null ? c1 : getFileContentType(f)) {
                @Override
                public void writeTo(OutputStream outstream) throws IOException {
                    super.writeTo(decorateBodyOutput(outstream));
                }
            };
        } else {
            String s = o2s(o1, "");
            String encoding = selectValue(fieldsEncoding, prepared != null ? prepared.fieldsEncoding : null,
                    "UTF-8");
            try {
                StringEntity entity;
                entity = new StringEntity(s, encoding) {
                    @Override
                    public void writeTo(OutputStream outstream) throws IOException {
                        super.writeTo(decorateBodyOutput(outstream));
                    }
                };
                entity.setContentType(c1 != null ? c1 : DEFAULT_TEXT_CONTENT_TYPE);
                return entity;
            } catch (UnsupportedEncodingException e) {
                throw new RoboqException("Illegal encoding for request body", e);
            }
        }
    }

    private static boolean isBytesEquivalent(Object obj) {
        return obj instanceof byte[] || obj instanceof InputStream || obj instanceof File;
    }

    private HttpEntity createFieldsBody(Map<String, Object> fields) {
        // multipart post?
        Boolean multipart1 = selectValue(multipart, prepared != null ? prepared.multipart : null, null);
        if (multipart1 == null) {
            // auto
            for (Object o : fields.values()) {
                if (isBytesEquivalent(o instanceof Body ? ((Body) o).content : o)) {
                    multipart1 = true;
                    break;
                }
            }
            if (multipart1 == null)
                multipart1 = false;
        } else if (multipart1 != null && !multipart1) {
            for (Object o : fields.values()) {
                if (isBytesEquivalent(o instanceof Body ? ((Body) o).content : o))
                    throw new RoboqException("URLEncodedForm not support the type (byte[], InputStream, File)");
            }
        }

        String encoding = selectValue(fieldsEncoding, prepared != null ? prepared.fieldsEncoding : null, "UTF-8");
        try {
            if (multipart1) {
                MultipartEntity entity = new MultipartEntity(HttpMultipartMode.STRICT) {
                    @Override
                    public void writeTo(OutputStream outstream) throws IOException {
                        super.writeTo(decorateBodyOutput(outstream));
                    }
                };
                for (Map.Entry<String, Object> entry : fields.entrySet()) {
                    String field = entry.getKey();
                    Object o = entry.getValue();
                    Object o1 = o instanceof Body ? ((Body) o).content : o;
                    String c1 = o instanceof Body ? ((Body) o).contentType : null;
                    String f1 = o instanceof Body ? ((Body) o).filename : null;
                    if (isBytesEquivalent(o1)) {
                        if (o1 instanceof byte[]) {
                            ByteArrayBody cb = new ByteArrayBody((byte[]) o1,
                                    c1 != null ? c1 : DEFAULT_BINARY_CONTENT_TYPE, f1 != null ? f1 : "");
                            entity.addPart(field, cb);
                        } else if (o1 instanceof InputStream) {
                            Long len = o instanceof Body ? ((Body) o).length : null;
                            if (len == null)
                                throw new RoboqException("Missing length in body for upload InputStream");
                            InputStreamBody cb = new InputStreamBody((InputStream) o1,
                                    c1 != null ? c1 : DEFAULT_BINARY_CONTENT_TYPE, f1 != null ? f1 : "", len);
                            entity.addPart(field, cb);
                        } else if (o1 instanceof File) {
                            File f = (File) o1;
                            FileBody cb = new FileBody(f, f1 != null ? f1 : null,
                                    c1 != null ? c1 : getFileContentType(f), encoding);
                            entity.addPart(field, cb);
                        } else {
                            throw new RoboqException("Here is unreached");
                        }
                    } else {
                        String s = o2s(o1, "");
                        StringBody cb = new StringBody(s, c1 != null ? c1 : DEFAULT_TEXT_CONTENT_TYPE,
                                Charset.forName(encoding)) {
                            @Override
                            public void writeTo(OutputStream out) throws IOException {
                                super.writeTo(decorateBodyOutput(out));
                            }
                        };
                        entity.addPart(field, cb);
                    }
                }
                return entity;
            } else {
                ArrayList<NameValuePair> nvl = new ArrayList<NameValuePair>();
                for (Map.Entry<String, Object> entry : fields.entrySet()) {
                    Object o = entry.getValue();
                    Object o1 = o instanceof Body ? ((Body) o).content : o;
                    nvl.add(new BasicNameValuePair(entry.getKey(), o2s(o1, "")));
                }

                return new UrlEncodedFormEntity(nvl, encoding) {
                    @Override
                    public void writeTo(OutputStream outstream) throws IOException {
                        super.writeTo(decorateBodyOutput(outstream));
                    }
                };
            }
        } catch (UnsupportedEncodingException e) {
            throw new RoboqException(e);
        }
    }

    private static String getMethodAsText(int method) {
        switch (method) {
        case GET:
            return "GET";
        case POST:
            return "POST";
        case PUT:
            return "PUT";
        case DELETE:
            return "DELETE";
        case OPTIONS:
            return "OPTIONS";
        case TRACE:
            return "TRACE";
        case HEAD:
            return "HEAD";
        }
        return "";
    }

    private static String formatRequest(int method, String url, Options opts) {
        StringBuilder buff = new StringBuilder();
        buff.append("HTTP ").append(getMethodAsText(method)).append(" ").append(url);
        // TODO: opts
        return buff.toString();
    }

    public Response execute() {
        int method = mergeMethod();
        String url = mergeUrl();
        Options opts = mergeOptions();

        HttpClient httpClient = createHttpClient(method, url, opts);
        HttpUriRequest httpReq = createHttpRequest(method, url, opts);
        try {
            Log.d(LOG_TAG, formatRequest(method, url, opts));
            HttpResponse httpResp = httpClient.execute(httpReq);
            return createResponse(httpResp);
        } catch (IOException e) {
            throw new RoboqException(e);
        }
    }

    public void execute(Executor executor, final Callback cb) {
        Executor executor1 = selectValue(executor, prepared != null ? prepared.executor : null, null);
        if (executor1 == null)
            throw new NullPointerException("The executor is null");

        executor1.execute(new Runnable() {
            @Override
            public void run() {
                Response resp;
                Exception error;
                try {
                    resp = execute();
                    error = null;
                } catch (Exception e) {
                    resp = null;
                    error = e;
                }
                if (cb != null)
                    cb.onResponse(Request.this, resp, error);
            }
        });
    }

    public void execute(Callback cb) {
        execute(null, cb);
    }

    public static class Options extends LinkedHashMap<String, Object> {
        public Options() {
        }

        public Options(Map<? extends String, ?> map) {
            super(map);
        }

        public Options(Object... kvs) {
            if (kvs == null)
                throw new NullPointerException("kvs is null");
            if (kvs.length % 2 != 0)
                throw new IllegalArgumentException("Illegal kvs pair");

            for (int i = 0; i < kvs.length; i += 2) {
                Object k = kvs[i];
                Object v = kvs[i + 1];
                if (k == null)
                    throw new NullPointerException("The key is null");

                put(k.toString(), v);
            }
        }

        public Options add(Map<String, Object> m) {
            if (m != null && !m.isEmpty()) {
                putAll(m);
            }
            return this;
        }

        public Options add(String k, Object v) {
            put(k, v);
            return this;
        }

        public Options addIf(boolean b, String k, Object v) {
            if (b) {
                put(k, v);
            }
            return this;
        }

        public Map<String, Object> getPathVars() {
            return filter("$");
        }

        public Map<String, Object> getHeaders() {
            return filter(":");
        }

        public Map<String, Object> getParams() {
            return filter("=");
        }

        public Map<String, Object> getFields() {
            return filter("@");
        }

        public Object getBody() {
            return get("@");
        }

        protected Map<String, Object> filter(String keyPostfix) {
            LinkedHashMap<String, Object> m = new LinkedHashMap<String, Object>();
            for (Map.Entry<String, Object> entry : this.entrySet()) {
                String k = entry.getKey();
                if (k != null && k.endsWith(keyPostfix)) {
                    k = k.substring(0, k.length() - keyPostfix.length()).trim();
                    if (k != null && k.length() > 0)
                        m.put(k, entry.getValue());
                }
            }
            return m;
        }
    }

    public static class Prepared extends RequestBase<Prepared> {

        protected Executor executor;

        public Prepared() {
        }

        public Prepared(String url) {
            this(url, (Options) null);
        }

        public Prepared(String url, Object... options) {
            this(url, new Options(options));
        }

        public Prepared(String url, Options options) {
            setUrl(url);
            with(options);
        }

        public Executor getExecutor() {
            return executor;
        }

        public Prepared setExecutor(Executor executor) {
            this.executor = executor;
            return this;
        }
    }

    public static interface Callback {
        void onResponse(Request req, Response resp, Exception error);
    }

    public static abstract class Modifier {

        protected Modifier() {
        }

        public int modifyMethod(int method) {
            return method;
        }

        public String modifyUrl(String url) {
            return url;
        }

        public Options modifyOptions(Options options) {
            return options;
        }
    }

    public static class Body {
        public final Object content;
        public final Long length;
        public final String contentType;
        public final String filename;

        Body(Object content, Long length, String contentType, String filename) {
            this.content = content;
            this.length = length;
            this.contentType = contentType;
            this.filename = filename;
        }
    }

    public static Body bytesBody(byte[] bytes, final String filename) {
        return bytesBody(bytes, null, filename);
    }

    public static Body bytesBody(byte[] bytes, String contentType, String filename) {
        return new Body(bytes, (long) bytes.length, contentType, filename);
    }

    public static Body contentBody(InputStream content, long length) {
        return contentBody(content, length, null, null);
    }

    public static Body contentBody(InputStream content, long length, String filename) {
        return contentBody(content, length, null, filename);
    }

    public static Body contentBody(InputStream content, long length, String contentType, String filename) {
        return new Body(content, length, contentType, filename);
    }

    public static Body fileBody(File file) {
        return fileBody(file, null, null);
    }

    public static Body fileBody(File file, String filename) {
        return fileBody(file, null, filename);
    }

    public static Body fileBody(File file, String contentType, String filename) {
        return new Body(file, null, contentType, filename);
    }

    public static Body textBody(String text, String contentType) {
        return new Body(text, null, contentType, null);
    }
}