001package com.gigya.android.sdk.network.adapter;
002
003import android.content.Context;
004import android.support.annotation.GuardedBy;
005import android.support.annotation.NonNull;
006import android.support.annotation.Nullable;
007
008import com.android.volley.AuthFailureError;
009import com.android.volley.DefaultRetryPolicy;
010import com.android.volley.NetworkResponse;
011import com.android.volley.ParseError;
012import com.android.volley.Request;
013import com.android.volley.RequestQueue;
014import com.android.volley.Response;
015import com.android.volley.VolleyError;
016import com.android.volley.VolleyLog;
017import com.android.volley.toolbox.HttpHeaderParser;
018import com.android.volley.toolbox.Volley;
019import com.gigya.android.sdk.GigyaLogger;
020import com.gigya.android.sdk.api.GigyaApiRequest;
021import com.gigya.android.sdk.api.GigyaApiHttpRequest;
022import com.gigya.android.sdk.api.IApiRequestFactory;
023import com.gigya.android.sdk.network.GigyaError;
024import com.gigya.android.sdk.utils.UrlUtils;
025
026import java.util.HashMap;
027import java.util.Iterator;
028import java.util.Map;
029import java.util.Queue;
030import java.util.concurrent.ConcurrentLinkedQueue;
031import java.util.concurrent.TimeUnit;
032
033public class VolleyNetworkProvider extends NetworkProvider {
034
035    private static final String LOG_TAG = "VolleyNetworkProvider";
036
037    private RequestQueue _requestQueue;
038    private Queue<HttpVolleyTask> _blockedQueue = new ConcurrentLinkedQueue<>();
039
040    VolleyNetworkProvider(IApiRequestFactory requestFactory, Context appContext) {
041        super(requestFactory);
042        _requestQueue = Volley.newRequestQueue(appContext);
043        // Enable Volley logs.
044        VolleyLog.DEBUG = GigyaLogger.isDebug();
045    }
046
047    public static boolean isAvailable() {
048        try {
049            Class.forName("com.android.volley.toolbox.Volley");
050            return true;
051        } catch (Exception ex) {
052            return false;
053        }
054    }
055
056    @Override
057    public void addToQueue(GigyaApiRequest request, IRestAdapterCallback networkCallbacks) {
058        _requestQueue.getCache().clear();
059
060        if (_blocked) {
061            GigyaLogger.debug(LOG_TAG, "addToQueue: is blocked. adding to blocked queued - " + request.getApi());
062            _blockedQueue.add(new HttpVolleyTask(request, networkCallbacks));
063            return;
064        }
065        if (!_blockedQueue.isEmpty()) {
066            GigyaLogger.debug(LOG_TAG, "addToQueue: blockedQueue is empty releasing it - " + request.getApi());
067            release();
068        }
069
070        GigyaLogger.debug(LOG_TAG, "addToQueue: adding to queue - " + request.getApi());
071
072        _requestFactory.sign(request);
073        VolleyNetworkRequest newRequest = createRequest(request, networkCallbacks);
074        _requestQueue.add(newRequest);
075    }
076
077    @Override
078    public void sendBlocking(GigyaApiRequest request, IRestAdapterCallback networkCallbacks) {
079        GigyaLogger.debug(LOG_TAG, "sendBlocking: " + request.getApi());
080        _requestQueue.getCache().clear();
081
082        _requestFactory.sign(request);
083        VolleyNetworkRequest newRequest = createRequest(request, networkCallbacks);
084        _requestQueue.add(newRequest);
085        _blocked = true;
086    }
087
088    @Override
089    public void release() {
090        super.release();
091        if (_blockedQueue.isEmpty()) {
092            return;
093        }
094
095        // Traverse over blocked queue and release all.
096        while (!_blockedQueue.isEmpty()) {
097
098            final HttpVolleyTask task = _blockedQueue.poll();
099            // Need to resign the request.
100            _requestFactory.sign(task.getRequest());
101
102            final VolleyNetworkRequest queued = createRequest(task.getRequest(), task.getNetworkCallbacks());
103            GigyaLogger.debug(LOG_TAG, "release: polled request  - " + queued.getUrl());
104
105            _requestQueue.add(queued);
106        }
107    }
108
109    @Override
110    public void cancel(String tag) {
111        if (tag == null) {
112            // Cancel all.
113            _requestQueue.cancelAll(new RequestQueue.RequestFilter() {
114
115                @Override
116                public boolean apply(Request<?> request) {
117                    return true;
118                }
119            });
120            _blockedQueue.clear();
121            return;
122        }
123        _requestQueue.cancelAll(tag);
124        if (!_blockedQueue.isEmpty()) {
125            Iterator it = _blockedQueue.iterator();
126            while (it.hasNext()) {
127                final HttpVolleyTask task = (HttpVolleyTask) it.next();
128                final String requestTag = task.request.getTag();
129                if (requestTag.equals(tag)) {
130                    it.remove();
131                }
132            }
133        }
134    }
135
136    //region VOLLEY SPECIFIC IMPLEMENTATION
137
138    private static class HttpVolleyTask {
139
140        private GigyaApiRequest request;
141        private final IRestAdapterCallback networkCallbacks;
142
143        private HttpVolleyTask(GigyaApiRequest request, IRestAdapterCallback networkCallbacks) {
144            this.request = request;
145            this.networkCallbacks = networkCallbacks;
146        }
147
148        public GigyaApiRequest getRequest() {
149            return request;
150        }
151
152        public void setRequest(GigyaApiRequest request) {
153            this.request = request;
154        }
155
156        public IRestAdapterCallback getNetworkCallbacks() {
157            return networkCallbacks;
158        }
159    }
160
161    /*
162    Generate a new Volley request.
163     */
164    private VolleyNetworkRequest createRequest(final GigyaApiRequest request, final IRestAdapterCallback networkCallbacks) {
165
166        final GigyaApiHttpRequest signedRequest = _requestFactory.sign(request);
167
168        return new VolleyNetworkRequest(
169                request.getMethod().intValue(),
170                signedRequest.getUrl(),
171                new Response.Listener<VolleyResponsePair>() {
172                    @Override
173                    public void onResponse(VolleyResponsePair response) {
174                        GigyaLogger.debug("GigyaApiResponse", "ApiService: " + signedRequest.getUrl() + "\n" + response);
175                        if (networkCallbacks != null) {
176                            networkCallbacks.onResponse(response.res, response.date);
177                        }
178                    }
179                },
180                new Response.ErrorListener() {
181                    @Override
182                    public void onErrorResponse(VolleyError error) {
183                        int errorCode = 0;
184                        if (error.networkResponse != null) {
185                            errorCode = error.networkResponse.statusCode;
186                        }
187                        final String localizedMessage = error.getLocalizedMessage() == null ? "" : error.getLocalizedMessage();
188                        final GigyaError gigyaError = new GigyaError(errorCode, localizedMessage, null);
189                        GigyaLogger.debug("GigyaApiResponse", "GigyaApiResponse: Error " +
190                                "ApiService: " + signedRequest.getUrl() + "\n" +
191                                gigyaError.toString());
192                        if (networkCallbacks != null) {
193                            networkCallbacks.onError(gigyaError);
194                        }
195                    }
196                },
197                signedRequest.getEncodedParams(),
198                request.getTag()
199        );
200    }
201
202    private static class VolleyNetworkRequest extends Request<VolleyResponsePair> {
203
204        /**
205         * Lock to guard mListener as it is cleared on cancel() and read on delivery.
206         */
207        private final Object _lock = new Object();
208
209        @Nullable
210        @GuardedBy("mLock")
211        private Response.Listener<VolleyResponsePair> _listener;
212
213        @Nullable
214        private String _body;
215
216        VolleyNetworkRequest(int method,
217                             String url,
218                             @NonNull Response.Listener<VolleyResponsePair> listener,
219                             @NonNull Response.ErrorListener errorListener,
220                             @Nullable String body,
221                             String tag) {
222            super(method, url, errorListener);
223            setTag(tag);
224            _body = body;
225            _listener = listener;
226            setShouldCache(false);
227            setRetryPolicy(new DefaultRetryPolicy(
228                    (int) TimeUnit.SECONDS.toMillis(30), //After the set time elapses the request will timeout
229                    0,
230                    DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
231        }
232
233        @Override
234        public Map<String, String> getHeaders() {
235            Map<String, String> headers = new HashMap<>();
236            headers.put("Accept-Encoding", "gzip, deflate");
237            headers.put("connection", "close");
238            return headers;
239        }
240
241        @Override
242        public byte[] getBody() throws AuthFailureError {
243            if (_body != null) {
244                return this._body.getBytes();
245            }
246            return super.getBody();
247        }
248
249        @Override
250        public void cancel() {
251            super.cancel();
252            synchronized (_lock) {
253                _listener = null;
254            }
255        }
256
257        @Override
258        protected void deliverResponse(VolleyResponsePair response) {
259            Response.Listener<VolleyResponsePair> listener;
260            synchronized (_lock) {
261                listener = _listener;
262            }
263            if (listener != null) {
264                listener.onResponse(response);
265            }
266        }
267
268        @Override
269        protected Response<VolleyResponsePair> parseNetworkResponse(NetworkResponse response) {
270            String jsonString;
271            try {
272                final String dateHeader = response.headers.get("Date");
273                final String encoding = response.headers.get("Content-Encoding");
274                if (encoding != null && encoding.equals("gzip")) {
275                    // Response contains GZIP encoding.
276                    jsonString = UrlUtils.gzipDecode(response.data);
277                } else {
278                    jsonString = new String(
279                            response.data,
280                            HttpHeaderParser.parseCharset(response.headers, "utf-8"));
281                }
282                return Response.success(
283                        new VolleyResponsePair(jsonString, dateHeader),
284                        HttpHeaderParser.parseCacheHeaders(response));
285            } catch (Exception e) {
286                return Response.error(new ParseError(e));
287            }
288        }
289    }
290
291    static class VolleyResponsePair {
292
293        final private String res;
294        final private String date;
295
296        VolleyResponsePair(String res, String date) {
297            this.res = res;
298            this.date = date;
299        }
300    }
301
302    //endregion
303
304}