Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14792 manas 1
/**
2
 * Copyright 2010-present Facebook.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *    http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
package com.facebook;
18
 
19
import android.content.Context;
20
import com.facebook.internal.CacheableRequestBatch;
21
import com.facebook.internal.FileLruCache;
22
import com.facebook.internal.Logger;
23
import com.facebook.internal.Utility;
24
import com.facebook.model.GraphObject;
25
import com.facebook.model.GraphObjectList;
26
import org.json.JSONArray;
27
import org.json.JSONException;
28
import org.json.JSONObject;
29
import org.json.JSONTokener;
30
 
31
import java.io.IOException;
32
import java.io.InputStream;
33
import java.net.HttpURLConnection;
34
import java.net.MalformedURLException;
35
import java.net.URL;
36
import java.util.ArrayList;
37
import java.util.List;
38
 
39
/**
40
 * Encapsulates the response, successful or otherwise, of a call to the Facebook platform.
41
 */
42
public class Response {
43
    private final HttpURLConnection connection;
44
    private final GraphObject graphObject;
45
    private final GraphObjectList<GraphObject> graphObjectList;
46
    private final boolean isFromCache;
47
    private final FacebookRequestError error;
48
    private final String rawResponse;
49
    private final Request request;
50
 
51
    /**
52
     * Property name of non-JSON results in the GraphObject. Certain calls to Facebook result in a non-JSON response
53
     * (e.g., the string literal "true" or "false"). To present a consistent way of accessing results, these are
54
     * represented as a GraphObject with a single string property with this name.
55
     */
56
    public static final String NON_JSON_RESPONSE_PROPERTY = "FACEBOOK_NON_JSON_RESULT";
57
 
58
    // From v2.1 of the Graph API, write endpoints will now return valid JSON with the result as the value for the "success" key
59
    public static final String SUCCESS_KEY = "success";
60
 
61
    private static final int INVALID_SESSION_FACEBOOK_ERROR_CODE = 190;
62
 
63
    private static final String CODE_KEY = "code";
64
    private static final String BODY_KEY = "body";
65
 
66
    private static final String RESPONSE_LOG_TAG = "Response";
67
 
68
    private static final String RESPONSE_CACHE_TAG = "ResponseCache";
69
    private static FileLruCache responseCache;
70
 
71
    Response(Request request, HttpURLConnection connection, String rawResponse, GraphObject graphObject, boolean isFromCache) {
72
        this(request, connection, rawResponse, graphObject, null, isFromCache, null);
73
    }
74
 
75
    Response(Request request, HttpURLConnection connection, String rawResponse, GraphObjectList<GraphObject> graphObjects,
76
            boolean isFromCache) {
77
        this(request, connection, rawResponse, null, graphObjects, isFromCache, null);
78
    }
79
 
80
    Response(Request request, HttpURLConnection connection, FacebookRequestError error) {
81
        this(request, connection, null, null, null, false, error);
82
    }
83
 
84
    Response(Request request, HttpURLConnection connection, String rawResponse, GraphObject graphObject, GraphObjectList<GraphObject> graphObjects, boolean isFromCache, FacebookRequestError error) {
85
        this.request = request;
86
        this.connection = connection;
87
        this.rawResponse = rawResponse;
88
        this.graphObject = graphObject;
89
        this.graphObjectList = graphObjects;
90
        this.isFromCache = isFromCache;
91
        this.error = error;
92
    }
93
 
94
    /**
95
     * Returns information about any errors that may have occurred during the request.
96
     *
97
     * @return the error from the server, or null if there was no server error
98
     */
99
    public final FacebookRequestError getError() {
100
        return error;
101
    }
102
 
103
    /**
104
     * The single graph object returned for this request, if any.
105
     *
106
     * @return the graph object returned, or null if none was returned (or if the result was a list)
107
     */
108
    public final GraphObject getGraphObject() {
109
        return graphObject;
110
    }
111
 
112
    /**
113
     * The single graph object returned for this request, if any, cast into a particular type of GraphObject.
114
     *
115
     * @param graphObjectClass the GraphObject-derived interface to cast the graph object into
116
     * @return the graph object returned, or null if none was returned (or if the result was a list)
117
     * @throws FacebookException If the passed in Class is not a valid GraphObject interface
118
     */
119
    public final <T extends GraphObject> T getGraphObjectAs(Class<T> graphObjectClass) {
120
        if (graphObject == null) {
121
            return null;
122
        }
123
        if (graphObjectClass == null) {
124
            throw new NullPointerException("Must pass in a valid interface that extends GraphObject");
125
        }
126
        return graphObject.cast(graphObjectClass);
127
    }
128
 
129
    /**
130
     * The list of graph objects returned for this request, if any.
131
     *
132
     * @return the list of graph objects returned, or null if none was returned (or if the result was not a list)
133
     */
134
    public final GraphObjectList<GraphObject> getGraphObjectList() {
135
        return graphObjectList;
136
    }
137
 
138
    /**
139
     * The list of graph objects returned for this request, if any, cast into a particular type of GraphObject.
140
     *
141
     * @param graphObjectClass the GraphObject-derived interface to cast the graph objects into
142
     * @return the list of graph objects returned, or null if none was returned (or if the result was not a list)
143
     * @throws FacebookException If the passed in Class is not a valid GraphObject interface
144
     */
145
    public final <T extends GraphObject> GraphObjectList<T> getGraphObjectListAs(Class<T> graphObjectClass) {
146
        if (graphObjectList == null) {
147
            return null;
148
        }
149
        return graphObjectList.castToListOf(graphObjectClass);
150
    }
151
 
152
    /**
153
     * Returns the HttpURLConnection that this response was generated from. If the response was retrieved
154
     * from the cache, this will be null.
155
     *
156
     * @return the connection, or null
157
     */
158
    public final HttpURLConnection getConnection() {
159
        return connection;
160
    }
161
 
162
    /**
163
     * Returns the request that this response is for.
164
     *
165
     * @return the request that this response is for
166
     */
167
    public Request getRequest() {
168
        return request;
169
    }
170
 
171
    /**
172
     * Returns the server response as a String that this response is for.
173
     *
174
     * @return A String representation of the actual response from the server
175
     */
176
    public String getRawResponse() {
177
        return rawResponse;
178
    }
179
 
180
    /**
181
     * Indicates whether paging is being done forward or backward.
182
     */
183
    public enum PagingDirection {
184
        /**
185
         * Indicates that paging is being performed in the forward direction.
186
         */
187
        NEXT,
188
        /**
189
         * Indicates that paging is being performed in the backward direction.
190
         */
191
        PREVIOUS
192
    }
193
 
194
    /**
195
     * If a Response contains results that contain paging information, returns a new
196
     * Request that will retrieve the next page of results, in whichever direction
197
     * is desired. If no paging information is available, returns null.
198
     *
199
     * @param direction enum indicating whether to page forward or backward
200
     * @return a Request that will retrieve the next page of results in the desired
201
     *         direction, or null if no paging information is available
202
     */
203
    public Request getRequestForPagedResults(PagingDirection direction) {
204
        String link = null;
205
        if (graphObject != null) {
206
            PagedResults pagedResults = graphObject.cast(PagedResults.class);
207
            PagingInfo pagingInfo = pagedResults.getPaging();
208
            if (pagingInfo != null) {
209
                if (direction == PagingDirection.NEXT) {
210
                    link = pagingInfo.getNext();
211
                } else {
212
                    link = pagingInfo.getPrevious();
213
                }
214
            }
215
        }
216
        if (Utility.isNullOrEmpty(link)) {
217
            return null;
218
        }
219
 
220
        if (link != null && link.equals(request.getUrlForSingleRequest())) {
221
            // We got the same "next" link as we just tried to retrieve. This could happen if cached
222
            // data is invalid. All we can do in this case is pretend we have finished.
223
            return null;
224
        }
225
 
226
        Request pagingRequest;
227
        try {
228
            pagingRequest = new Request(request.getSession(), new URL(link));
229
        } catch (MalformedURLException e) {
230
            return null;
231
        }
232
 
233
        return pagingRequest;
234
    }
235
 
236
    /**
237
     * Provides a debugging string for this response.
238
     */
239
    @Override
240
    public String toString() {
241
        String responseCode;
242
        try {
243
            responseCode = String.format("%d", (connection != null) ? connection.getResponseCode() : 200);
244
        } catch (IOException e) {
245
            responseCode = "unknown";
246
        }
247
 
248
        return new StringBuilder().append("{Response: ").append(" responseCode: ").append(responseCode)
249
                .append(", graphObject: ").append(graphObject).append(", error: ").append(error)
250
                .append(", isFromCache:").append(isFromCache).append("}")
251
                .toString();
252
    }
253
 
254
    /**
255
     * Indicates whether the response was retrieved from a local cache or from the server.
256
     *
257
     * @return true if the response was cached locally, false if it was retrieved from the server
258
     */
259
    public final boolean getIsFromCache() {
260
        return isFromCache;
261
    }
262
 
263
    static FileLruCache getResponseCache() {
264
        if (responseCache == null) {
265
            Context applicationContext = Session.getStaticContext();
266
            if (applicationContext != null) {
267
                responseCache = new FileLruCache(applicationContext, RESPONSE_CACHE_TAG, new FileLruCache.Limits());
268
            }
269
        }
270
 
271
        return responseCache;
272
    }
273
 
274
    @SuppressWarnings("resource")
275
    static List<Response> fromHttpConnection(HttpURLConnection connection, RequestBatch requests) {
276
        InputStream stream = null;
277
 
278
        FileLruCache cache = null;
279
        String cacheKey = null;
280
        if (requests instanceof CacheableRequestBatch) {
281
            CacheableRequestBatch cacheableRequestBatch = (CacheableRequestBatch) requests;
282
            cache = getResponseCache();
283
            cacheKey = cacheableRequestBatch.getCacheKeyOverride();
284
            if (Utility.isNullOrEmpty(cacheKey)) {
285
                if (requests.size() == 1) {
286
                    // Default for single requests is to use the URL.
287
                    cacheKey = requests.get(0).getUrlForSingleRequest();
288
                } else {
289
                    Logger.log(LoggingBehavior.REQUESTS, RESPONSE_CACHE_TAG,
290
                            "Not using cache for cacheable request because no key was specified");
291
                }
292
            }
293
 
294
            // Try loading from cache.  If that fails, load from the network.
295
            if (!cacheableRequestBatch.getForceRoundTrip() && cache != null && !Utility.isNullOrEmpty(cacheKey)) {
296
                try {
297
                    stream = cache.get(cacheKey);
298
                    if (stream != null) {
299
                        return createResponsesFromStream(stream, null, requests, true);
300
                    }
301
                } catch (FacebookException exception) { // retry via roundtrip below
302
                } catch (JSONException exception) {
303
                } catch (IOException exception) {
304
                } finally {
305
                    Utility.closeQuietly(stream);
306
                }
307
            }
308
        }
309
 
310
        // Load from the network, and cache the result if not an error.
311
        try {
312
            if (connection.getResponseCode() >= 400) {
313
                stream = connection.getErrorStream();
314
            } else {
315
                stream = connection.getInputStream();
316
                if ((cache != null) && (cacheKey != null) && (stream != null)) {
317
                    InputStream interceptStream = cache.interceptAndPut(cacheKey, stream);
318
                    if (interceptStream != null) {
319
                        stream = interceptStream;
320
                    }
321
                }
322
            }
323
 
324
            return createResponsesFromStream(stream, connection, requests, false);
325
        } catch (FacebookException facebookException) {
326
            Logger.log(LoggingBehavior.REQUESTS, RESPONSE_LOG_TAG, "Response <Error>: %s", facebookException);
327
            return constructErrorResponses(requests, connection, facebookException);
328
        } catch (JSONException exception) {
329
            Logger.log(LoggingBehavior.REQUESTS, RESPONSE_LOG_TAG, "Response <Error>: %s", exception);
330
            return constructErrorResponses(requests, connection, new FacebookException(exception));
331
        } catch (IOException exception) {
332
            Logger.log(LoggingBehavior.REQUESTS, RESPONSE_LOG_TAG, "Response <Error>: %s", exception);
333
            return constructErrorResponses(requests, connection, new FacebookException(exception));
334
        } catch (SecurityException exception) {
335
            Logger.log(LoggingBehavior.REQUESTS, RESPONSE_LOG_TAG, "Response <Error>: %s", exception);
336
            return constructErrorResponses(requests, connection, new FacebookException(exception));
337
        } finally {
338
            Utility.closeQuietly(stream);
339
        }
340
    }
341
 
342
    static List<Response> createResponsesFromStream(InputStream stream, HttpURLConnection connection,
343
            RequestBatch requests, boolean isFromCache) throws FacebookException, JSONException, IOException {
344
 
345
        String responseString = Utility.readStreamToString(stream);
346
        Logger.log(LoggingBehavior.INCLUDE_RAW_RESPONSES, RESPONSE_LOG_TAG,
347
                "Response (raw)\n  Size: %d\n  Response:\n%s\n", responseString.length(),
348
                responseString);
349
 
350
        return createResponsesFromString(responseString, connection, requests, isFromCache);
351
    }
352
 
353
    static List<Response> createResponsesFromString(String responseString, HttpURLConnection connection,
354
            RequestBatch requests, boolean isFromCache) throws FacebookException, JSONException, IOException {
355
        JSONTokener tokener = new JSONTokener(responseString);
356
        Object resultObject = tokener.nextValue();
357
 
358
        List<Response> responses = createResponsesFromObject(connection, requests, resultObject, isFromCache);
359
        Logger.log(LoggingBehavior.REQUESTS, RESPONSE_LOG_TAG, "Response\n  Id: %s\n  Size: %d\n  Responses:\n%s\n",
360
                requests.getId(), responseString.length(), responses);
361
 
362
        return responses;
363
    }
364
 
365
    private static List<Response> createResponsesFromObject(HttpURLConnection connection, List<Request> requests,
366
            Object object, boolean isFromCache) throws FacebookException, JSONException {
367
        assert (connection != null) || isFromCache;
368
 
369
        int numRequests = requests.size();
370
        List<Response> responses = new ArrayList<Response>(numRequests);
371
        Object originalResult = object;
372
 
373
        if (numRequests == 1) {
374
            Request request = requests.get(0);
375
            try {
376
                // Single request case -- the entire response is the result, wrap it as "body" so we can handle it
377
                // the same as we do in the batched case. We get the response code from the actual HTTP response,
378
                // as opposed to the batched case where it is returned as a "code" element.
379
                JSONObject jsonObject = new JSONObject();
380
                jsonObject.put(BODY_KEY, object);
381
                int responseCode = (connection != null) ? connection.getResponseCode() : 200;
382
                jsonObject.put(CODE_KEY, responseCode);
383
 
384
                JSONArray jsonArray = new JSONArray();
385
                jsonArray.put(jsonObject);
386
 
387
                // Pretend we got an array of 1 back.
388
                object = jsonArray;
389
            } catch (JSONException e) {
390
                responses.add(new Response(request, connection, new FacebookRequestError(connection, e)));
391
            } catch (IOException e) {
392
                responses.add(new Response(request, connection, new FacebookRequestError(connection, e)));
393
            }
394
        }
395
 
396
        if (!(object instanceof JSONArray) || ((JSONArray) object).length() != numRequests) {
397
            FacebookException exception = new FacebookException("Unexpected number of results");
398
            throw exception;
399
        }
400
 
401
        JSONArray jsonArray = (JSONArray) object;
402
 
403
        for (int i = 0; i < jsonArray.length(); ++i) {
404
            Request request = requests.get(i);
405
            try {
406
                Object obj = jsonArray.get(i);
407
                responses.add(createResponseFromObject(request, connection, obj, isFromCache, originalResult));
408
            } catch (JSONException e) {
409
                responses.add(new Response(request, connection, new FacebookRequestError(connection, e)));
410
            } catch (FacebookException e) {
411
                responses.add(new Response(request, connection, new FacebookRequestError(connection, e)));
412
            }
413
        }
414
 
415
        return responses;
416
    }
417
 
418
    private static Response createResponseFromObject(Request request, HttpURLConnection connection, Object object,
419
            boolean isFromCache, Object originalResult) throws JSONException {
420
        if (object instanceof JSONObject) {
421
            JSONObject jsonObject = (JSONObject) object;
422
 
423
            FacebookRequestError error =
424
                    FacebookRequestError.checkResponseAndCreateError(jsonObject, originalResult, connection);
425
            if (error != null) {
426
                if (error.getErrorCode() == INVALID_SESSION_FACEBOOK_ERROR_CODE) {
427
                    Session session = request.getSession();
428
                    if (session != null) {
429
                        session.closeAndClearTokenInformation();
430
                    }
431
                }
432
                return new Response(request, connection, error);
433
            }
434
 
435
            Object body = Utility.getStringPropertyAsJSON(jsonObject, BODY_KEY, NON_JSON_RESPONSE_PROPERTY);
436
 
437
            if (body instanceof JSONObject) {
438
                GraphObject graphObject = GraphObject.Factory.create((JSONObject) body);
439
                return new Response(request, connection, body.toString(), graphObject, isFromCache);
440
            } else if (body instanceof JSONArray) {
441
                GraphObjectList<GraphObject> graphObjectList = GraphObject.Factory.createList(
442
                        (JSONArray) body, GraphObject.class);
443
                return new Response(request, connection, body.toString(), graphObjectList, isFromCache);
444
            }
445
            // We didn't get a body we understand how to handle, so pretend we got nothing.
446
            object = JSONObject.NULL;
447
        }
448
 
449
        if (object == JSONObject.NULL) {
450
            return new Response(request, connection, object.toString(), (GraphObject)null, isFromCache);
451
        } else {
452
            throw new FacebookException("Got unexpected object type in response, class: "
453
                    + object.getClass().getSimpleName());
454
        }
455
    }
456
 
457
    static List<Response> constructErrorResponses(List<Request> requests, HttpURLConnection connection,
458
            FacebookException error) {
459
        int count = requests.size();
460
        List<Response> responses = new ArrayList<Response>(count);
461
        for (int i = 0; i < count; ++i) {
462
            Response response = new Response(requests.get(i), connection, new FacebookRequestError(connection, error));
463
            responses.add(response);
464
        }
465
        return responses;
466
    }
467
 
468
    interface PagingInfo extends GraphObject {
469
        String getNext();
470
 
471
        String getPrevious();
472
    }
473
 
474
    interface PagedResults extends GraphObject {
475
        GraphObjectList<GraphObject> getData();
476
 
477
        PagingInfo getPaging();
478
    }
479
 
480
}