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 com.facebook.android.R;
20
import com.facebook.internal.Utility;
21
import org.json.JSONException;
22
import org.json.JSONObject;
23
 
24
import java.net.HttpURLConnection;
25
 
26
/**
27
 * This class represents an error that occurred during a Facebook request.
28
 * <p/>
29
 * In general, one would call {@link #getCategory()} to determine the type
30
 * of error that occurred, and act accordingly. The app can also call
31
 * {@link #getUserActionMessageId()} in order to get the resource id for a
32
 * string that can be displayed to the user. For more information on error
33
 * handling, see <a href="https://developers.facebook.com/docs/reference/api/errors/">
34
 * https://developers.facebook.com/docs/reference/api/errors/</a>
35
 */
36
public final class FacebookRequestError {
37
 
38
    /** Represents an invalid or unknown error code from the server. */
39
    public static final int INVALID_ERROR_CODE = -1;
40
 
41
    /**
42
     * Indicates that there was no valid HTTP status code returned, indicating
43
     * that either the error occurred locally, before the request was sent, or
44
     * that something went wrong with the HTTP connection. Check the exception
45
     * from {@link #getException()};
46
     */
47
    public static final int INVALID_HTTP_STATUS_CODE = -1;
48
 
49
    private static final int INVALID_MESSAGE_ID = 0;
50
 
51
    private static final String CODE_KEY = "code";
52
    private static final String BODY_KEY = "body";
53
    private static final String ERROR_KEY = "error";
54
    private static final String ERROR_TYPE_FIELD_KEY = "type";
55
    private static final String ERROR_CODE_FIELD_KEY = "code";
56
    private static final String ERROR_MESSAGE_FIELD_KEY = "message";
57
    private static final String ERROR_CODE_KEY = "error_code";
58
    private static final String ERROR_SUB_CODE_KEY = "error_subcode";
59
    private static final String ERROR_MSG_KEY = "error_msg";
60
    private static final String ERROR_REASON_KEY = "error_reason";
61
    private static final String ERROR_USER_TITLE_KEY = "error_user_title";
62
    private static final String ERROR_USER_MSG_KEY = "error_user_msg";
63
    private static final String ERROR_IS_TRANSIENT_KEY = "is_transient";
64
 
65
    private static class Range {
66
        private final int start, end;
67
 
68
        private Range(int start, int end) {
69
            this.start = start;
70
            this.end = end;
71
        }
72
 
73
        boolean contains(int value) {
74
            return start <= value && value <= end;
75
        }
76
    }
77
 
78
    private static final int EC_UNKNOWN_ERROR = 1;
79
    private static final int EC_SERVICE_UNAVAILABLE = 2;
80
    private static final int EC_APP_TOO_MANY_CALLS = 4;
81
    private static final int EC_USER_TOO_MANY_CALLS = 17;
82
    private static final int EC_PERMISSION_DENIED = 10;
83
    private static final int EC_INVALID_SESSION = 102;
84
    private static final int EC_INVALID_TOKEN = 190;
85
    private static final Range EC_RANGE_PERMISSION = new Range(200, 299);
86
    private static final int EC_APP_NOT_INSTALLED = 458;
87
    private static final int EC_USER_CHECKPOINTED = 459;
88
    private static final int EC_PASSWORD_CHANGED = 460;
89
    private static final int EC_EXPIRED = 463;
90
    private static final int EC_UNCONFIRMED_USER = 464;
91
 
92
    private static final Range HTTP_RANGE_SUCCESS = new Range(200, 299);
93
    private static final Range HTTP_RANGE_CLIENT_ERROR = new Range(400, 499);
94
    private static final Range HTTP_RANGE_SERVER_ERROR = new Range(500, 599);
95
 
96
    private final int userActionMessageId;
97
    private final boolean shouldNotifyUser;
98
    private final Category category;
99
    private final int requestStatusCode;
100
    private final int errorCode;
101
    private final int subErrorCode;
102
    private final String errorType;
103
    private final String errorMessage;
104
    private final String errorUserTitle;
105
    private final String errorUserMessage;
106
    private final boolean errorIsTransient;
107
    private final JSONObject requestResult;
108
    private final JSONObject requestResultBody;
109
    private final Object batchRequestResult;
110
    private final HttpURLConnection connection;
111
    private final FacebookException exception;
112
 
113
    private FacebookRequestError(int requestStatusCode, int errorCode,
114
            int subErrorCode, String errorType, String errorMessage, String errorUserTitle, String errorUserMessage,
115
            boolean errorIsTransient, JSONObject requestResultBody, JSONObject requestResult, Object batchRequestResult,
116
            HttpURLConnection connection, FacebookException exception) {
117
        this.requestStatusCode = requestStatusCode;
118
        this.errorCode = errorCode;
119
        this.subErrorCode = subErrorCode;
120
        this.errorType = errorType;
121
        this.errorMessage = errorMessage;
122
        this.requestResultBody = requestResultBody;
123
        this.requestResult = requestResult;
124
        this.batchRequestResult = batchRequestResult;
125
        this.connection = connection;
126
        this.errorUserTitle = errorUserTitle;
127
        this.errorUserMessage = errorUserMessage;
128
        this.errorIsTransient = errorIsTransient;
129
 
130
        boolean isLocalException = false;
131
        if (exception != null) {
132
            this.exception = exception;
133
            isLocalException =  true;
134
        } else {
135
            this.exception = new FacebookServiceException(this, errorMessage);
136
        }
137
 
138
        // Initializes the error categories based on the documented error codes as outlined here
139
        // https://developers.facebook.com/docs/reference/api/errors/
140
        Category errorCategory = null;
141
        int messageId = INVALID_MESSAGE_ID;
142
        boolean shouldNotify = false;
143
        if (isLocalException) {
144
            errorCategory = Category.CLIENT;
145
            messageId = INVALID_MESSAGE_ID;
146
        } else {
147
            if (errorCode == EC_UNKNOWN_ERROR || errorCode == EC_SERVICE_UNAVAILABLE) {
148
                errorCategory = Category.SERVER;
149
            } else if (errorCode == EC_APP_TOO_MANY_CALLS || errorCode == EC_USER_TOO_MANY_CALLS) {
150
                errorCategory = Category.THROTTLING;
151
            } else if (errorCode == EC_PERMISSION_DENIED || EC_RANGE_PERMISSION.contains(errorCode)) {
152
                errorCategory = Category.PERMISSION;
153
                messageId = R.string.com_facebook_requesterror_permissions;
154
            } else if (errorCode == EC_INVALID_SESSION || errorCode == EC_INVALID_TOKEN) {
155
                if (subErrorCode == EC_USER_CHECKPOINTED || subErrorCode == EC_UNCONFIRMED_USER) {
156
                    errorCategory = Category.AUTHENTICATION_RETRY;
157
                    messageId = R.string.com_facebook_requesterror_web_login;
158
                    shouldNotify = true;
159
                } else {
160
                    errorCategory = Category.AUTHENTICATION_REOPEN_SESSION;
161
 
162
                    if ((subErrorCode == EC_APP_NOT_INSTALLED) || (subErrorCode == EC_EXPIRED)) {
163
                        messageId = R.string.com_facebook_requesterror_relogin;
164
                    } else if (subErrorCode == EC_PASSWORD_CHANGED) {
165
                        messageId = R.string.com_facebook_requesterror_password_changed;
166
                    } else {
167
                        messageId = R.string.com_facebook_requesterror_reconnect;
168
                        shouldNotify = true;
169
                    }
170
                }
171
            }
172
 
173
            if (errorCategory == null) {
174
                if (HTTP_RANGE_CLIENT_ERROR.contains(requestStatusCode)) {
175
                    errorCategory = Category.BAD_REQUEST;
176
                } else if (HTTP_RANGE_SERVER_ERROR.contains(requestStatusCode)) {
177
                    errorCategory = Category.SERVER;
178
                } else {
179
                    errorCategory = Category.OTHER;
180
                }
181
            }
182
        }
183
 
184
        // Notify user when error_user_msg is present
185
        shouldNotify = errorUserMessage!= null && errorUserMessage.length() > 0;
186
 
187
        this.category = errorCategory;
188
        this.userActionMessageId = messageId;
189
        this.shouldNotifyUser = shouldNotify;
190
    }
191
 
192
    private FacebookRequestError(int requestStatusCode, int errorCode,
193
            int subErrorCode, String errorType, String errorMessage, String errorUserTitle, String errorUserMessage,
194
            boolean errorIsTransient, JSONObject requestResultBody, JSONObject requestResult, Object batchRequestResult,
195
            HttpURLConnection connection) {
196
        this(requestStatusCode, errorCode, subErrorCode, errorType, errorMessage, errorUserTitle, errorUserMessage,
197
                errorIsTransient, requestResultBody, requestResult, batchRequestResult, connection, null);
198
    }
199
 
200
    FacebookRequestError(HttpURLConnection connection, Exception exception) {
201
        this(INVALID_HTTP_STATUS_CODE, INVALID_ERROR_CODE, INVALID_ERROR_CODE,
202
                null, null, null, null, false, null, null, null, connection,
203
                (exception instanceof FacebookException) ?
204
                        (FacebookException) exception : new FacebookException(exception));
205
    }
206
 
207
    public FacebookRequestError(int errorCode, String errorType, String errorMessage) {
208
        this(INVALID_HTTP_STATUS_CODE, errorCode, INVALID_ERROR_CODE, errorType, errorMessage,
209
                null, null, false, null, null, null, null, null);
210
    }
211
 
212
    /**
213
     * Returns the resource id for a user-friendly message for the application to
214
     * present to the user.
215
     *
216
     * @return a user-friendly message to present to the user
217
     */
218
    public int getUserActionMessageId() {
219
        return userActionMessageId;
220
    }
221
 
222
    /**
223
     * Returns whether direct user action is required to successfully continue with the Facebook
224
     * operation. If user action is required, apps can also call {@link #getUserActionMessageId()}
225
     * in order to get a resource id for a message to show the user.
226
     *
227
     * @return whether direct user action is required
228
     */
229
    public boolean shouldNotifyUser() {
230
        return shouldNotifyUser;
231
    }
232
 
233
    /**
234
     * Returns the category in which the error belongs. Applications can use the category
235
     * to determine how best to handle the errors (e.g. exponential backoff for retries if
236
     * being throttled).
237
     *
238
     * @return the category in which the error belong
239
     */
240
    public Category getCategory() {
241
        return category;
242
    }
243
 
244
    /**
245
     * Returns the HTTP status code for this particular request.
246
     *
247
     * @return the HTTP status code for the request
248
     */
249
    public int getRequestStatusCode() {
250
        return requestStatusCode;
251
    }
252
 
253
    /**
254
     * Returns the error code returned from Facebook.
255
     *
256
     * @return the error code returned from Facebook
257
     */
258
    public int getErrorCode() {
259
        return errorCode;
260
    }
261
 
262
    /**
263
     * Returns the sub-error code returned from Facebook.
264
     *
265
     * @return the sub-error code returned from Facebook
266
     */
267
    public int getSubErrorCode() {
268
        return subErrorCode;
269
    }
270
 
271
    /**
272
     * Returns the type of error as a raw string. This is generally less useful
273
     * than using the {@link #getCategory()} method, but can provide further details
274
     * on the error.
275
     *
276
     * @return the type of error as a raw string
277
     */
278
    public String getErrorType() {
279
        return errorType;
280
    }
281
 
282
    /**
283
     * Returns the error message returned from Facebook.
284
     *
285
     * @return the error message returned from Facebook
286
     */
287
    public String getErrorMessage() {
288
        if (errorMessage != null) {
289
            return errorMessage;
290
        } else {
291
            return exception.getLocalizedMessage();
292
        }
293
    }
294
 
295
    /**
296
     * A message suitable for display to the user, describing a user action necessary to enable Facebook functionality.
297
     * Not all Facebook errors yield a message suitable for user display; however in all cases where
298
     * shouldNotifyUser() returns true, this method returns a non-null message suitable for display.
299
     *
300
     * @return the error message returned from Facebook
301
     */
302
    public String getErrorUserMessage() {
303
        return errorUserMessage;
304
    }
305
 
306
    /**
307
     * A short summary of the error suitable for display to the user.
308
     * Not all Facebook errors yield a title/message suitable for user display; however in all cases where
309
     * getErrorUserTitle() returns valid String - user should be notified.
310
     *
311
     * @return the error message returned from Facebook
312
     */
313
    public String getErrorUserTitle() {
314
        return errorUserTitle;
315
    }
316
 
317
    /**
318
     * @return true if given error is transient and may succeed if the initial action is retried as-is.
319
     * Application may use this information to display a "Retry" button, if user should be notified about this error.
320
     */
321
    public boolean getErrorIsTransient() {
322
        return errorIsTransient;
323
    }
324
 
325
    /**
326
     * Returns the body portion of the response corresponding to the request from Facebook.
327
     *
328
     * @return the body of the response for the request
329
     */
330
    public JSONObject getRequestResultBody() {
331
        return requestResultBody;
332
    }
333
 
334
    /**
335
     * Returns the full JSON response for the corresponding request. In a non-batch request,
336
     * this would be the raw response in the form of a JSON object. In a batch request, this
337
     * result will contain the body of the response as well as the HTTP headers that pertain
338
     * to the specific request (in the form of a "headers" JSONArray).
339
     *
340
     * @return the full JSON response for the request
341
     */
342
    public JSONObject getRequestResult() {
343
        return requestResult;
344
    }
345
 
346
    /**
347
     * Returns the full JSON response for the batch request. If the request was not a batch
348
     * request, then the result from this method is the same as {@link #getRequestResult()}.
349
     * In case of a batch request, the result will be a JSONArray where the elements
350
     * correspond to the requests in the batch. Callers should check the return type against
351
     * either JSONObject or JSONArray and cast accordingly.
352
     *
353
     * @return the full JSON response for the batch
354
     */
355
    public Object getBatchRequestResult() {
356
        return batchRequestResult;
357
    }
358
 
359
    /**
360
     * Returns the HTTP connection that was used to make the request.
361
     *
362
     * @return the HTTP connection used to make the request
363
     */
364
    public HttpURLConnection getConnection() {
365
        return connection;
366
    }
367
 
368
    /**
369
     * Returns the exception associated with this request, if any.
370
     *
371
     * @return the exception associated with this request
372
     */
373
    public FacebookException getException() {
374
        return exception;
375
    }
376
 
377
    @Override
378
    public String toString() {
379
        return new StringBuilder("{HttpStatus: ")
380
                .append(requestStatusCode)
381
                .append(", errorCode: ")
382
                .append(errorCode)
383
                .append(", errorType: ")
384
                .append(errorType)
385
                .append(", errorMessage: ")
386
                .append(getErrorMessage())
387
                .append("}")
388
                .toString();
389
    }
390
 
391
    static FacebookRequestError checkResponseAndCreateError(JSONObject singleResult,
392
            Object batchResult, HttpURLConnection connection) {
393
        try {
394
            if (singleResult.has(CODE_KEY)) {
395
                int responseCode = singleResult.getInt(CODE_KEY);
396
                Object body = Utility.getStringPropertyAsJSON(singleResult, BODY_KEY,
397
                        Response.NON_JSON_RESPONSE_PROPERTY);
398
 
399
                if (body != null && body instanceof JSONObject) {
400
                    JSONObject jsonBody = (JSONObject) body;
401
                    // Does this response represent an error from the service? We might get either an "error"
402
                    // with several sub-properties, or else one or more top-level fields containing error info.
403
                    String errorType = null;
404
                    String errorMessage = null;
405
                    String errorUserMessage = null;
406
                    String errorUserTitle = null;
407
                    boolean errorIsTransient = false;
408
                    int errorCode = INVALID_ERROR_CODE;
409
                    int errorSubCode = INVALID_ERROR_CODE;
410
 
411
                    boolean hasError = false;
412
                    if (jsonBody.has(ERROR_KEY)) {
413
                        // We assume the error object is correctly formatted.
414
                        JSONObject error = (JSONObject) Utility.getStringPropertyAsJSON(jsonBody, ERROR_KEY, null);
415
 
416
                        errorType = error.optString(ERROR_TYPE_FIELD_KEY, null);
417
                        errorMessage = error.optString(ERROR_MESSAGE_FIELD_KEY, null);
418
                        errorCode = error.optInt(ERROR_CODE_FIELD_KEY, INVALID_ERROR_CODE);
419
                        errorSubCode = error.optInt(ERROR_SUB_CODE_KEY, INVALID_ERROR_CODE);
420
                        errorUserMessage =  error.optString(ERROR_USER_MSG_KEY, null);
421
                        errorUserTitle =  error.optString(ERROR_USER_TITLE_KEY, null);
422
                        errorIsTransient = error.optBoolean(ERROR_IS_TRANSIENT_KEY, false);
423
                        hasError = true;
424
                    } else if (jsonBody.has(ERROR_CODE_KEY) || jsonBody.has(ERROR_MSG_KEY)
425
                            || jsonBody.has(ERROR_REASON_KEY)) {
426
                        errorType = jsonBody.optString(ERROR_REASON_KEY, null);
427
                        errorMessage = jsonBody.optString(ERROR_MSG_KEY, null);
428
                        errorCode = jsonBody.optInt(ERROR_CODE_KEY, INVALID_ERROR_CODE);
429
                        errorSubCode = jsonBody.optInt(ERROR_SUB_CODE_KEY, INVALID_ERROR_CODE);
430
                        hasError = true;
431
                    }
432
 
433
                    if (hasError) {
434
                        return new FacebookRequestError(responseCode, errorCode, errorSubCode,
435
                                errorType, errorMessage, errorUserTitle, errorUserMessage, errorIsTransient, jsonBody,
436
                                singleResult, batchResult, connection);
437
                    }
438
                }
439
 
440
                // If we didn't get error details, but we did get a failure response code, report it.
441
                if (!HTTP_RANGE_SUCCESS.contains(responseCode)) {
442
                    return new FacebookRequestError(responseCode, INVALID_ERROR_CODE,
443
                            INVALID_ERROR_CODE, null, null, null, null, false,
444
                            singleResult.has(BODY_KEY) ?
445
                                    (JSONObject) Utility.getStringPropertyAsJSON(
446
                                            singleResult, BODY_KEY, Response.NON_JSON_RESPONSE_PROPERTY) : null,
447
                            singleResult, batchResult, connection);
448
                }
449
            }
450
        } catch (JSONException e) {
451
            // defer the throwing of a JSONException to the graph object proxy
452
        }
453
        return null;
454
    }
455
 
456
    /**
457
     * An enum that represents the Facebook SDK classification for the error that occurred.
458
     */
459
    public enum Category {
460
        /**
461
         * Indicates that the error is authentication related, and that the app should retry
462
         * the request after some user action.
463
         */
464
        AUTHENTICATION_RETRY,
465
 
466
        /**
467
         * Indicates that the error is authentication related, and that the app should close
468
         * the session and reopen it.
469
         */
470
        AUTHENTICATION_REOPEN_SESSION,
471
 
472
        /** Indicates that the error is permission related. */
473
        PERMISSION,
474
 
475
        /**
476
         * Indicates that the error implies the server had an unexpected failure or may be
477
         * temporarily unavailable.
478
         */
479
        SERVER,
480
 
481
        /** Indicates that the error results from the server throttling the client. */
482
        THROTTLING,
483
 
484
        /**
485
         * Indicates that the error is Facebook-related but cannot be categorized at this time,
486
         * and is likely newer than the current version of the SDK.
487
         */
488
        OTHER,
489
 
490
        /**
491
         * Indicates that the error is an application error resulting in a bad or malformed
492
         * request to the server.
493
         */
494
        BAD_REQUEST,
495
 
496
        /**
497
         * Indicates that this is a client-side error. Examples of this can include, but are
498
         * not limited to, JSON parsing errors or {@link java.io.IOException}s.
499
         */
500
        CLIENT
501
    };
502
 
503
}