package im.amomo.volley;
import com.android.volley.AuthFailureError;
import com.android.volley.Cache;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.RetryPolicy;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.Authenticator;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by GoogolMo on 12/18/13.
*/
public abstract class OkRequest<T> extends Request<T> {
/**
* 'Authorization' header name
*/
public static final String HEADER_AUTHORIZATION = "Authorization";
/**
* 'UTF-8' charset name
*/
public static final String CHARSET_UTF8 = "UTF-8";
/**
* 'gzip' encoding header value
*/
public static final String ENCODING_GZIP = "gzip";
/**
* 'Accept' header name
*/
public static final String HEADER_ACCEPT = "Accept";
/**
* 'Accept-Charset' header name
*/
public static final String HEADER_ACCEPT_CHARSET = "Accept-Charset";
/**
* 'Accept-Encoding' header name
*/
public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
/**
* 'User-Agent' header name
*/
public static final String HEADER_USER_AGENT = "User-Agent";
/**
* 'X-Accept-Version' header name
*/
public static final String HEADER_ACCEPT_VERSION = "X-Accept-Version";
/**
* 'Content-Type' header name
*/
public static final String HEADER_CONTENT_TYPE = "Content-Type";
/**
* 'Content-Length' header name
*/
public static final String HEADER_CONTENT_LENGTH = "Content-Length";
/**
* 'Content-Encoding' header name
*/
public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
/**
* 'Referer' header name
*/
public static final String HEADER_REFERER = "Referer";
/**
* Content type for request.
*/
public static final String PROTOCOL_CONTENT_TYPE_JSON =
String.format("application/json; charset=%s", CHARSET_UTF8);
public static final String PROTOCOL_CONTENT_TYPE_FORM =
String.format("application/x-www-form-urlencoded; charset=%s", CHARSET_UTF8);
/**
* 'charset' header value parameter
*/
public static final String PARAM_CHARSET = "charset";
private static final String BOUNDARY = "00douban0natalya0volley00";
private static final String CONTENT_TYPE_MULTIPART = "multipart/form-data; boundary="
+ BOUNDARY;
private static final String CRLF = "\r\n";
protected ConcurrentHashMap<String, String> mRequestHeaders;
// protected ConcurrentHashMap<String, String> mRequestParams;
private Response.Listener mListener;
private String mContentType = PROTOCOL_CONTENT_TYPE_FORM;
private boolean mMultipart;
private boolean mForm;
private RequestOutputStream mOutput;
private int mBufferSize = 8192;
private boolean mIgnoreCloseExceptions = true;
private String mRequestUrl;
/**
* construct method
*
* @param method request method
* @param url request url
* @param errorListener error listener see {@link com.android.volley.Response.ErrorListener}
*/
public OkRequest(int method, String url, Response.ErrorListener errorListener) {
super(method, url, errorListener);
this.mRequestHeaders = new ConcurrentHashMap<String, String>();
}
/**
* construct method
*
* @param method request method
* @param url request url
* @param errorListener error listener see {@link com.android.volley.Response.ErrorListener}
*/
public OkRequest(int method, String url, Response.Listener<T> listener, Response.ErrorListener errorListener) {
super(method, url, errorListener);
this.mListener = listener;
this.mRequestHeaders = new ConcurrentHashMap<String, String>();
}
/**
* set response listener
*
* @param listener response listener @{@link com.android.volley.Response.Listener}
* @return this request
*/
public OkRequest<T> setReseponseListener(Response.Listener<T> listener) {
this.mListener = listener;
return this;
}
/**
* Set 'Content-Type' header
*
* @param contentType {@link String} contentType
* @return this request
*/
public OkRequest<T> contentType(final String contentType) {
this.mContentType = contentType;
return this;
}
protected OkRequest<T> openOutput() {
if (mOutput != null) {
return this;
}
mOutput = new RequestOutputStream(CHARSET_UTF8);
return this;
}
protected OkRequest<T> startPart() throws IOException {
openOutput();
if (!mMultipart) {
mMultipart = true;
contentType(CONTENT_TYPE_MULTIPART);
mOutput.write("--" + BOUNDARY + CRLF);
} else {
mOutput.write(CRLF + "--" + BOUNDARY + CRLF);
}
return this;
}
/**
* Write part header
*
* @param name
* @param filename
* @return this request
* @throws java.io.IOException
*/
protected OkRequest<T> writePartHeader(final String name, final String filename)
throws IOException {
return writePartHeader(name, filename, null);
}
/**
* Write part header
*
* @param name
* @param filename
* @param contentType
* @return this request
* @throws java.io.IOException
*/
protected OkRequest<T> writePartHeader(final String name,
final String filename, final String contentType) throws IOException {
final StringBuilder partBuffer = new StringBuilder();
partBuffer.append("form-data; name=\"").append(name);
if (filename != null) {
partBuffer.append("\"; filename=\"").append(filename);
}
partBuffer.append('"');
partHeader("Content-Disposition", partBuffer.toString());
if (contentType != null) {
partHeader(HEADER_CONTENT_TYPE, contentType);
}
return send(CRLF);
}
/**
* Write part of a multipart request to the request body
*
* @param name
* @param part
* @return this request
*/
public OkRequest<T> part(final String name, final String part) throws IOException {
return part(name, null, part);
}
/**
* Write part of a multipart request to the request body
*
* @param name
* @param filename
* @param part
* @return this request
* @throws OkRequest
*/
public OkRequest<T> part(final String name, final String filename,
final String part) throws IOException {
return part(name, filename, null, part);
}
/**
* Write part of a multipart request to the request body
*
* @param name
* @param filename
* @param contentType value of the Content-Type part header
* @param part
* @return this request
*/
public OkRequest<T> part(final String name, final String filename,
final String contentType, final String part) throws IOException {
startPart();
writePartHeader(name, filename, contentType);
mOutput.write(part);
return this;
}
/**
* Write part of a multipart request to the request body
*
* @param name
* @param part
* @return this request
*/
public OkRequest<T> part(final String name, final Number part) throws IOException {
return part(name, null, part);
}
/**
* Write part of a multipart request to the request body
*
* @param name
* @param filename
* @param part
* @return this request
*/
public OkRequest<T> part(final String name, final String filename,
final Number part) throws IOException {
return part(name, filename, part != null ? part.toString() : null);
}
/**
* Write part of a multipart request to the request body
*
* @param name
* @param part
* @return this request
*/
public OkRequest<T> part(final String name, final File part) throws IOException {
return part(name, null, part);
}
/**
* Write part of a multipart request to the request body
*
* @param name
* @param filename
* @param part
* @return this request
*/
public OkRequest<T> part(final String name, final String filename,
final File part) throws IOException {
return part(name, filename, null, part);
}
/**
* Write part of a multipart request to the request body
*
* @param name
* @param filename
* @param contentType value of the Content-Type part header
* @param part
* @return this request
*/
public OkRequest<T> part(final String name, final String filename,
final String contentType, final File part) throws IOException {
final InputStream stream;
stream = new BufferedInputStream(new FileInputStream(part));
// incrementTotalSize(part.length());
return part(name, filename, contentType, stream);
}
/**
* Write part of a multipart request to the request body
*
* @param name
* @param part
* @return this request
*/
public OkRequest<T> part(final String name, final InputStream part) throws IOException {
return part(name, null, null, part);
}
/**
* Write part of a multipart request to the request body
*
* @param name
* @param filename
* @param contentType value of the Content-Type part header
* @param part
* @return this request
*/
public OkRequest<T> part(final String name, final String filename,
final String contentType, final InputStream part) throws IOException {
try {
startPart();
writePartHeader(name, filename, contentType);
copy(part, mOutput);
} catch (IOException ex) {
throw ex;
} catch (Exception ex) {
VolleyLog.e(ex, "error on part");
}
return this;
}
/**
* Write a multipart header to the response body
*
* @param name
* @param value
* @return this request
*/
public OkRequest<T> partHeader(final String name, final String value) throws IOException {
return send(name).send(": ").send(value).send(CRLF);
}
/**
* Write contents of file to request body
*
* @param input
* @return this request
*/
public OkRequest<T> send(final File input) throws IOException {
final InputStream stream;
stream = new BufferedInputStream(new FileInputStream(input));
return send(stream);
}
/**
* Write byte array to request body
*
* @param input
* @return this request
*/
public OkRequest<T> send(final byte[] input) throws IOException {
return send(new ByteArrayInputStream(input));
}
/**
* Write stream to request body
* <p/>
* The given stream will be closed once sending completes
*
* @param input
* @return this request
*/
public OkRequest<T> send(final InputStream input) throws IOException {
openOutput();
copy(input, mOutput);
return this;
}
/**
* Write char sequence to request body
* <p/>
* The charset configured via {@link #contentType(String)} will be used and
* UTF-8 will be used if it is unset.
*
* @param value
* @return this request
*/
public OkRequest<T> send(final CharSequence value) {
openOutput();
try {
mOutput.write(value.toString());
} catch (IOException e) {
deliverError(new ParseError(e));
}
return this;
}
/**
* Write the values in the map as form data to the request body
* <p/>
* The pairs specified will be URL-encoded in UTF-8 and sent with the
* 'application/x-www-form-urlencoded' content-type
*
* @param values
* @return this request
*/
public OkRequest<T> form(final Map<String, String> values) {
return form(values, CHARSET_UTF8);
}
/**
* Write the key and value in the entry as form data to the request body
* <p/>
* The pair specified will be URL-encoded in UTF-8 and sent with the
* 'application/x-www-form-urlencoded' content-type
*
* @param entry
* @return this request
*/
public OkRequest<T> form(final Map.Entry<String, String> entry) {
return form(entry, CHARSET_UTF8);
}
/**
* Write the key and value in the entry as form data to the request body
* <p/>
* The pair specified will be URL-encoded and sent with the
* 'application/x-www-form-urlencoded' content-type
*
* @param entry
* @param charset
* @return this request
*/
public OkRequest<T> form(final Map.Entry<String, String> entry, final String charset) {
return form(entry.getKey(), entry.getValue(), charset);
}
/**
* Write the name/value pair as form data to the request body
* <p/>
* The pair specified will be URL-encoded in UTF-8 and sent with the
* 'application/x-www-form-urlencoded' content-type
*
* @param name
* @param value
* @return this request
*/
public OkRequest<T> form(final String name, final String value) {
return form(name, value, CHARSET_UTF8);
}
/**
* Write the name/value pair as form data to the request body
* <p/>
* The values specified will be URL-encoded and sent with the
* 'application/x-www-form-urlencoded' content-type
*
* @param name
* @param value
* @param charset
* @return this request
*/
public OkRequest<T> form(final String name, final String value, String charset) {
final boolean first = !mForm;
if (first) {
contentType(PROTOCOL_CONTENT_TYPE_FORM);
mForm = true;
}
charset = getValidCharset(charset);
openOutput();
if (!first) {
mOutput.write('&');
}
try {
if (VolleyLog.DEBUG) {
VolleyLog.d("name=%1$s, value=%2$s", name, value);
}
mOutput.write(URLEncoder.encode(name, charset));
mOutput.write("=");
if (value != null) {
mOutput.write(URLEncoder.encode(value, charset));
}
} catch (IOException e) {
//Do Nothing
}
return this;
}
/**
* Write the values in the map as encoded form data to the request body
*
* @param values
* @param charset
* @return this request
*/
public OkRequest<T> form(final Map<String, String> values, final String charset) {
if (!values.isEmpty()) {
for (Map.Entry<String, String> entry : values.entrySet()) {
form(entry, charset);
}
}
return this;
}
/**
* Write the value to url params
*
* @param key key
* @param value value
* @return this request
*/
public OkRequest<T> param(final String key, final String value) {
StringBuilder urlBuilder = new StringBuilder(getUrl());
if (getUrl().contains("?")) {
urlBuilder.append("&");
} else {
urlBuilder.append("?");
}
urlBuilder.append(key);
urlBuilder.append("=");
urlBuilder.append(value);
mRequestUrl = urlBuilder.toString();
return this;
}
/**
* Write the map entry to url params
*
* @param entry map entry
* @return this request
*/
public OkRequest<T> param(final Map.Entry<String, String> entry) {
return param(entry.getKey(), entry.getValue());
}
/**
* Write the map to url params
*
* @param values map
* @return this request
*/
public OkRequest<T> params(final Map<String, String> values) {
if (!values.isEmpty()) {
for (Map.Entry<String, String> entry : values.entrySet()) {
param(entry);
}
}
return this;
}
/**
* Set header name to given value
*
* @param name
* @param value
* @return this request
*/
public OkRequest<T> header(final String name, final String value) {
mRequestHeaders.put(name, value);
return this;
}
/**
* Set header name to given value
*
* @param name
* @param value
* @return this request
*/
public OkRequest<T> header(final String name, final Number value) {
return header(name, value != null ? value.toString() : null);
}
/**
* Set all headers found in given map where the keys are the header names and
* the values are the header values
*
* @param headers
* @return this request
*/
public OkRequest<T> headers(final Map<String, String> headers) {
if (!headers.isEmpty())
for (Map.Entry<String, String> header : headers.entrySet())
header(header);
return this;
}
/**
* Set header to have given entry's key as the name and value as the value
*
* @param header
* @return this request
*/
public OkRequest<T> header(final Map.Entry<String, String> header) {
return header(header.getKey(), header.getValue());
}
public OkRequest<T> setAuthenticator(Authenticator authenticator) throws AuthFailureError {
if (authenticator == null) return this;
// if (authenticator == null) {
// throw new NullPointerException("authenticator can not be null!");
// }
String accessToken = authenticator.getAuthToken();
if (accessToken != null) {
header(HEADER_AUTHORIZATION, String.format("Bearer %1$s", accessToken));
}
return this;
}
/**
* Set the 'Accept' header to given value
*
* @param accept
* @return this request
*/
public OkRequest<T> accept(final String accept) {
return header(HEADER_ACCEPT, accept);
}
/**
* Set the 'Accept' header to 'application/json'
*
* @return this request
*/
public OkRequest<T> acceptJson() {
return accept(PROTOCOL_CONTENT_TYPE_JSON);
}
/**
* Set the 'Accept-Encoding' header to given value
*
* @param acceptEncoding
* @return this request
*/
public OkRequest<T> acceptEncoding(final String acceptEncoding) {
return header(HEADER_ACCEPT_ENCODING, acceptEncoding);
}
/**
* Set the 'Accept-Encoding' header to 'gzip'
*
* @return this request
*/
public OkRequest<T> acceptGzipEncoding() {
return acceptEncoding(ENCODING_GZIP);
}
/**
* Set the 'Accept-Charset' header to given value
*
* @param acceptCharset
* @return this request
*/
public OkRequest<T> acceptCharset(final String acceptCharset) {
return header(HEADER_ACCEPT_CHARSET, acceptCharset);
}
/**
* Set the 'User-Agent' header to given value
*
* @param userAgent
* @return this request
*/
public OkRequest<T> userAgent(final String userAgent) {
return header(HEADER_USER_AGENT, userAgent);
}
/**
* Set the 'Referer' header to given value
*
* @param referer
* @return this request
*/
public OkRequest<T> referer(final String referer) {
return header(HEADER_REFERER, referer);
}
/**
* Copy from input stream to output stream
*
* @param input
* @param output
* @return this request
* @throws java.io.IOException
*/
protected OkRequest<T> copy(final InputStream input, final OutputStream output) throws IOException {
return new CloseOperation<OkRequest>(input, mIgnoreCloseExceptions) {
@Override
public OkRequest<T> call() throws IOException {
boolean thrown = false;
try {
final byte[] buffer = new byte[mBufferSize];
int read;
while ((read = input.read(buffer)) != -1) {
output.write(buffer, 0, read);
}
return OkRequest.this;
} catch (IOException e) {
thrown = true;
throw e;
} finally {
try {
done();
} catch (IOException e) {
if (!thrown) {
throw e;
}
}
}
}
}.call();
}
/**
* get request header
*/
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = super.getHeaders();
if (!mRequestHeaders.isEmpty()) {
if (headers.isEmpty()) {
return mRequestHeaders;
} else {
headers.putAll(mRequestHeaders);
}
}
return headers;
}
@Override
protected abstract Response<T> parseNetworkResponse(NetworkResponse response);
@Override
protected void deliverResponse(T t) {
if (this.mListener != null) {
this.mListener.onResponse(t);
}
}
/**
* get original request url
*
* @return original request url
*/
public String getOriginUrl() {
return super.getUrl();
}
/**
* get request url
*/
@Override
public String getUrl() {
if (mRequestUrl == null) {
mRequestUrl = super.getUrl();
}
return mRequestUrl;
}
@Override
@Deprecated
protected Map<String, String> getParams() throws AuthFailureError {
return super.getParams();
}
/**
* get request body content
*/
@Override
public String getBodyContentType() {
return this.mContentType;
}
/**
* get request body
*/
@Override
public byte[] getBody() throws AuthFailureError {
if (mOutput == null) {
openOutput();
}
try {
if (mMultipart) {
mOutput.write(CRLF + "--" + BOUNDARY + "--" + CRLF);
}
return mOutput.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return mOutput.toByteArray();
} finally {
try {
mOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
mOutput = null;
}
}
@Override
public OkRequest<T> setRequestQueue(RequestQueue requestQueue) {
super.setRequestQueue(requestQueue);
return this;
}
private static String getValidCharset(final String charset) {
if (charset != null && charset.length() > 0)
return charset;
else
return CHARSET_UTF8;
}
public static class RequestOutputStream extends ByteArrayOutputStream {
private final String charset;
public RequestOutputStream(final String charset) {
super();
this.charset = charset;
}
public RequestOutputStream write(final String value) throws IOException {
// final ByteBuffer bytes = encoder.encode(CharBuffer.wrap(value));
super.write(value.getBytes(Charset.forName(charset)));
return this;
}
}
/**
* Class that ensures a {@link java.io.Closeable} gets closed with proper exception
* handling.
*
* @param <V>
*/
protected static abstract class CloseOperation<V> implements Callable<V> {
private final Closeable closeable;
private final boolean ignoreCloseExceptions;
/**
* Create closer for operation
*
* @param closeable
* @param ignoreCloseExceptions
*/
protected CloseOperation(final Closeable closeable,
final boolean ignoreCloseExceptions) {
this.closeable = closeable;
this.ignoreCloseExceptions = ignoreCloseExceptions;
}
protected void done() throws IOException {
if (closeable instanceof Flushable)
((Flushable) closeable).flush();
if (ignoreCloseExceptions)
try {
closeable.close();
} catch (IOException e) {
// Ignored
}
else
closeable.close();
}
}
@Override
public int getMethod() {
return super.getMethod();
}
@Override
public Request<?> setTag(Object tag) {
return super.setTag(tag);
}
@Override
public Object getTag() {
return super.getTag();
}
@Override
public int getTrafficStatsTag() {
return super.getTrafficStatsTag();
}
@Override
public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
return super.setRetryPolicy(retryPolicy);
}
@Override
public void addMarker(String tag) {
super.addMarker(tag);
}
@Override
public String getCacheKey() {
return this.getMethod() + ":" +this.getUrl();
}
@Override
public Request<?> setCacheEntry(Cache.Entry entry) {
return super.setCacheEntry(entry);
}
@Override
public Cache.Entry getCacheEntry() {
return super.getCacheEntry();
}
@Override
public void cancel() {
super.cancel();
}
@Override
protected String getParamsEncoding() {
return super.getParamsEncoding();
}
@Override
public Priority getPriority() {
return super.getPriority();
}
@Override
public RetryPolicy getRetryPolicy() {
return super.getRetryPolicy();
}
@Override
public void markDelivered() {
super.markDelivered();
}
@Override
public boolean hasHadResponseDelivered() {
return super.hasHadResponseDelivered();
}
@Override
protected VolleyError parseNetworkError(VolleyError volleyError) {
return super.parseNetworkError(volleyError);
}
@Override
public void deliverError(VolleyError error) {
super.deliverError(error);
}
@Override
public int compareTo(Request<T> other) {
return super.compareTo(other);
}
@Override
public String toString() {
return super.toString();
}
@Override
public boolean isCanceled() {
return super.isCanceled();
}
}