package dk.slott.super_volley.requests; import android.util.Base64; import android.util.Log; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.stream.JsonReader; import com.android.volley.NetworkResponse; import com.android.volley.Response; import com.android.volley.Response.ErrorListener; import com.android.volley.Response.Listener; import com.android.volley.toolbox.HttpHeaderParser; import com.android.volley.toolbox.JsonRequest; import dk.slott.super_volley.config.Config; import dk.slott.super_volley.managers.DataManagerHelper; import java.io.ByteArrayInputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.message.BasicNameValuePair; import org.json.JSONObject; /** * Volley adapter for JSON requests that will be parsed into Java objects by Gson. */ public class GsonRequest<T> extends JsonRequest<T> { private static final String TAG = GsonRequest.class.getSimpleName(); private final Gson gson; private final Class<T> clazz; private final Listener<T> listener; /** * Make a request and return a parsed object from JSON. * Note that this request object supports caching! * * @param url URL of the request to make * @param clazz Relevant class object, for Gson's reflection * @param headers Map of request headers */ public GsonRequest(final int method, final String url, final JSONObject jsonRequest, final Class<T> clazz, final int ttl, final Listener<T> listener, final ErrorListener errorListener) { super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, errorListener); // MSH: Define the timeformat used in the returned json response. this.gson = new GsonBuilder().setDateFormat(Config.DATE_FORMAT).create(); this.clazz = clazz; this.listener = listener; } /** * MSH: Collect headers that are sent to the server (Request Headers) * Note that we append headers to the header array from the super class. */ @Override public Map<String, String> getHeaders() throws com.android.volley.AuthFailureError { final Map<String, String> params = new HashMap<String, String>(); final Iterator<Entry<String, String>> it = DataManagerHelper.getAuthParams().entrySet().iterator(); // MSH: POST auth params. if(Config.AUTH_METHOD == Config.AuthMethod.POST) { while (it.hasNext()) { final Map.Entry<String, String> entry = (Map.Entry<String, String>)it.next(); Log.d(TAG, "auth param: " + entry.getKey() + ": " + entry.getValue()); params.put(entry.getKey(), entry.getValue()); } } /** * MSH: HTTP BASIC AUTH. * http://stackoverflow.com/questions/16817980/how-does-one-use-basic-authentication-with-this-library * http://stackoverflow.com/questions/1968416/how-to-do-http-authentication-in-android * Can't really use key,value combo here so might have to rething this... * TODO: Should just have a method where we define a http basic auth and thats it - no key values! */ else { while (it.hasNext()) { final Map.Entry<String, String> entry = (Map.Entry<String, String>)it.next(); final String creds = String.format("%s:%s",entry.getKey(),entry.getValue()); final String auth = "Basic " + Base64.encodeToString(creds.getBytes(), Base64.NO_WRAP); params.put("Authorization", auth); } } return params; }; @Override protected void deliverResponse(final T response) { this.listener.onResponse(response); } /** * MSH: * Normally we would get a valid cacheEntry when parsing cache headers but because of "Varnish" we * get no-cache and thus a null cache entry. Because of this we do not store the received etag and * thus never send it back to the server when requesting new data. */ @Override protected Response<T> parseNetworkResponse(final NetworkResponse response) { Log.d(TAG, "parseNetworkResponse"); // MSH: Ensure ETag is processed correctly by parseCacheHeaders. if(response.headers.get("ETag") != null) { Log.d(TAG, "Adding missing Cache-Control header to allow etag to be handled correctly"); // http://www.mnot.net/cache_docs/ response.headers.put("Cache-Control", "max-age=86400; must-revalidate"); } // MSH: Stream parse response to avoid converting it to a String first. final ByteArrayInputStream inputStream = new ByteArrayInputStream(response.data); final InputStreamReader inputStreamReader = new InputStreamReader(inputStream); final JsonReader reader = new JsonReader(inputStreamReader); final T o = gson.fromJson(reader, this.clazz); return Response.success(o, HttpHeaderParser.parseCacheHeaders(response)); } /** * MSH: Let the server know what we are sending. */ @Override public String getBodyContentType() { return Config.REQUEST_BODY_CONTENT_TYPE.toString() + "; charset=" + getParamsEncoding(); } /** * MSH: Convert supplied JSON body to a well formed POST body if request body content type is FORM */ @Override public byte[] getBody() { if(Config.REQUEST_BODY_CONTENT_TYPE == Config.BodyContentType.FORM) { byte[] body = super.getBody(); if(body != null) { final String jsonBody = new String(body); final List<BasicNameValuePair> paramsAsList = new ArrayList<BasicNameValuePair>(); try { final JSONObject jObject = new JSONObject(jsonBody); final Iterator<?> keys = jObject.keys(); while(keys.hasNext()) { final String key = (String)keys.next(); final Object value = jObject.get(key); if(value instanceof Integer) paramsAsList.add(new BasicNameValuePair(key, (((Integer)value)+""))); else paramsAsList.add(new BasicNameValuePair(key, value.toString())); } return URLEncodedUtils.format(paramsAsList, getParamsEncoding()).getBytes(getParamsEncoding()); } catch (Exception e) { Log.e(TAG, "Exception: " + e); } } } // MSH: Default data is already json else return super.getBody(); return null; } }