001/**
002 * Copyright (c) 2010-2019 Mark Allen, Norbert Bartels.
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a copy
005 * of this software and associated documentation files (the "Software"), to deal
006 * in the Software without restriction, including without limitation the rights
007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008 * copies of the Software, and to permit persons to whom the Software is
009 * furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in
012 * all copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
020 * THE SOFTWARE.
021 */
022package com.restfb;
023
024import static com.restfb.util.UrlUtils.extractParametersFromQueryString;
025import static java.lang.String.format;
026import static java.util.Collections.unmodifiableList;
027
028import java.util.ArrayList;
029import java.util.Date;
030import java.util.List;
031import java.util.Map;
032
033import com.restfb.batch.BatchRequest;
034import com.restfb.batch.BatchResponse;
035import com.restfb.exception.FacebookException;
036import com.restfb.exception.FacebookOAuthException;
037import com.restfb.exception.FacebookSignedRequestParsingException;
038import com.restfb.exception.FacebookSignedRequestVerificationException;
039import com.restfb.exception.devicetoken.FacebookDeviceTokenCodeExpiredException;
040import com.restfb.exception.devicetoken.FacebookDeviceTokenDeclinedException;
041import com.restfb.exception.devicetoken.FacebookDeviceTokenPendingException;
042import com.restfb.exception.devicetoken.FacebookDeviceTokenSlowdownException;
043import com.restfb.json.JsonObject;
044import com.restfb.scope.ScopeBuilder;
045import com.restfb.types.AbstractFacebookType;
046import com.restfb.types.DeviceCode;
047import com.restfb.util.ReflectionUtils;
048
049/**
050 * Specifies how a <a href="http://developers.facebook.com/docs/api">Facebook Graph API</a> client must operate.
051 * <p>
052 * If you'd like to...
053 * 
054 * <ul>
055 * <li>Fetch an object: use {@link #fetchObject(String, Class, Parameter...)} or
056 * {@link #fetchObjects(List, Class, Parameter...)}</li>
057 * <li>Fetch a connection: use {@link #fetchConnection(String, Class, Parameter...)}</li>
058 * <li>Execute operations in batch: use {@link #executeBatch(BatchRequest...)} or {@link #executeBatch(List, List)}</li>
059 * <li>Publish data: use {@link #publish(String, Class, Parameter...)} or
060 * {@link #publish(String, Class, BinaryAttachment, Parameter...)}</li>
061 * <li>Delete an object: use {@link #deleteObject(String, Parameter...)}</li>
062 * </ul>
063 * 
064 * <p>
065 * You may also perform some common access token operations. If you'd like to...
066 * 
067 * <ul>
068 * <li>Extend the life of an access token: use {@link #obtainExtendedAccessToken(String, String, String)}</li>
069 * <li>Obtain an access token for use on behalf of an application instead of a user, use
070 * {@link #obtainAppAccessToken(String, String)}.</li>
071 * <li>Convert old-style session keys to OAuth access tokens: use
072 * {@link #convertSessionKeysToAccessTokens(String, String, String...)}</li>
073 * <li>Verify and extract data from a signed request: use {@link #parseSignedRequest(String, String, Class)}</li>
074 * </ul>
075 * 
076 * @author <a href="http://restfb.com">Mark Allen</a>
077 * @author Scott Hernandez
078 * @author Mattia Tommasone
079 * @author <a href="http://ex-nerd.com">Chris Petersen</a>
080 * @author Josef Gierbl
081 * @author Broc Seib
082 */
083public interface FacebookClient {
084  /**
085   * Fetches a single <a href="http://developers.facebook.com/docs/reference/api/">Graph API object</a>, mapping the
086   * result to an instance of {@code objectType}.
087   * 
088   * @param <T>
089   *          Java type to map to.
090   * @param object
091   *          ID of the object to fetch, e.g. {@code "me"}.
092   * @param objectType
093   *          Object type token.
094   * @param parameters
095   *          URL parameters to include in the API call (optional).
096   * @return An instance of type {@code objectType} which contains the requested object's data.
097   * @throws FacebookException
098   *           If an error occurs while performing the API call.
099   */
100  <T> T fetchObject(String object, Class<T> objectType, Parameter... parameters);
101
102  /**
103   * Fetches multiple <a href="http://developers.facebook.com/docs/reference/api/">Graph API objects</a> in a single
104   * call, mapping the results to an instance of {@code objectType}.
105   * <p>
106   * You'll need to write your own container type ({@code objectType}) to hold the results. See
107   * <a href="http://restfb.com">http://restfb.com</a> for an example of how to do this.
108   * 
109   * @param <T>
110   *          Java type to map to.
111   * @param ids
112   *          IDs of the objects to fetch, e.g. {@code "me", "arjun"}.
113   * @param objectType
114   *          Object type token.
115   * @param parameters
116   *          URL parameters to include in the API call (optional).
117   * @return An instance of type {@code objectType} which contains the requested objects' data.
118   * @throws FacebookException
119   *           If an error occurs while performing the API call.
120   */
121  <T> T fetchObjects(List<String> ids, Class<T> objectType, Parameter... parameters);
122
123  /**
124   * Fetches a Graph API {@code Connection} type, mapping the result to an instance of {@code connectionType}.
125   * 
126   * @param <T>
127   *          Java type to map to.
128   * @param connection
129   *          The name of the connection, e.g. {@code "me/feed"}.
130   * @param connectionType
131   *          Connection type token.
132   * @param parameters
133   *          URL parameters to include in the API call (optional).
134   * @return An instance of type {@code connectionType} which contains the requested Connection's data.
135   * @throws FacebookException
136   *           If an error occurs while performing the API call.
137   */
138  <T> Connection<T> fetchConnection(String connection, Class<T> connectionType, Parameter... parameters);
139
140  /**
141   * Fetches a previous/next page of a Graph API {@code Connection} type, mapping the result to an instance of
142   * {@code connectionType}.
143   * 
144   * @param <T>
145   *          Java type to map to.
146   * @param connectionPageUrl
147   *          The URL of the connection page to fetch, usually retrieved via {@link Connection#getPreviousPageUrl()} or
148   *          {@link Connection#getNextPageUrl()}.
149   * @param connectionType
150   *          Connection type token.
151   * @return An instance of type {@code connectionType} which contains the requested Connection's data.
152   * @throws FacebookException
153   *           If an error occurs while performing the API call.
154   */
155  <T> Connection<T> fetchConnectionPage(String connectionPageUrl, Class<T> connectionType);
156
157  /**
158   * Executes operations as a batch using the <a href="https://developers.facebook.com/docs/reference/api/batch/">Batch
159   * API</a>.
160   * 
161   * @param batchRequests
162   *          The operations to execute.
163   * @return The execution results in the order in which the requests were specified.
164   */
165  List<BatchResponse> executeBatch(BatchRequest... batchRequests);
166
167  /**
168   * Executes operations as a batch using the <a href="https://developers.facebook.com/docs/reference/api/batch/">Batch
169   * API</a>.
170   * 
171   * @param batchRequests
172   *          The operations to execute.
173   * @return The execution results in the order in which the requests were specified.
174   */
175  List<BatchResponse> executeBatch(List<BatchRequest> batchRequests);
176
177  /**
178   * Executes operations as a batch with binary attachments using the
179   * <a href="https://developers.facebook.com/docs/reference/api/batch/">Batch API</a>.
180   * 
181   * @param batchRequests
182   *          The operations to execute.
183   * @param binaryAttachments
184   *          Binary attachments referenced by the batch requests.
185   * @return The execution results in the order in which the requests were specified.
186   * @since 1.6.5
187   */
188  List<BatchResponse> executeBatch(List<BatchRequest> batchRequests, List<BinaryAttachment> binaryAttachments);
189
190  /**
191   * Performs a <a href="http://developers.facebook.com/docs/api#publishing">Graph API publish</a> operation on the
192   * given {@code connection}, mapping the result to an instance of {@code objectType}.
193   * 
194   * @param <T>
195   *          Java type to map to.
196   * @param connection
197   *          The Connection to publish to.
198   * @param objectType
199   *          Object type token.
200   * @param parameters
201   *          URL parameters to include in the API call.
202   * @return An instance of type {@code objectType} which contains the Facebook response to your publish request.
203   * @throws FacebookException
204   *           If an error occurs while performing the API call.
205   */
206  <T> T publish(String connection, Class<T> objectType, Parameter... parameters);
207
208  /**
209   * Performs a <a href="http://developers.facebook.com/docs/api#publishing">Graph API publish</a> operation on the
210   * given {@code connection} and includes some files - photos, for example - in the publish request, and mapping the
211   * result to an instance of {@code objectType}.
212   * 
213   * @param <T>
214   *          Java type to map to.
215   * @param connection
216   *          The Connection to publish to.
217   * @param objectType
218   *          Object type token.
219   * @param binaryAttachments
220   *          The files to include in the publish request.
221   * @param parameters
222   *          URL parameters to include in the API call.
223   * @return An instance of type {@code objectType} which contains the Facebook response to your publish request.
224   * @throws FacebookException
225   *           If an error occurs while performing the API call.
226   */
227  <T> T publish(String connection, Class<T> objectType, List<BinaryAttachment> binaryAttachments,
228      Parameter... parameters);
229
230  /**
231   * Performs a <a href="http://developers.facebook.com/docs/api#publishing">Graph API publish</a> operation on the
232   * given {@code connection} and includes a file - a photo, for example - in the publish request, and mapping the
233   * result to an instance of {@code objectType}.
234   * 
235   * @param <T>
236   *          Java type to map to.
237   * @param connection
238   *          The Connection to publish to.
239   * @param objectType
240   *          Object type token.
241   * @param binaryAttachment
242   *          The file to include in the publish request.
243   * @param parameters
244   *          URL parameters to include in the API call.
245   * @return An instance of type {@code objectType} which contains the Facebook response to your publish request.
246   * @throws FacebookException
247   *           If an error occurs while performing the API call.
248   */
249  <T> T publish(String connection, Class<T> objectType, BinaryAttachment binaryAttachment, Parameter... parameters);
250
251  /**
252   * Performs a <a href="http://developers.facebook.com/docs/api#deleting">Graph API delete</a> operation on the given
253   * {@code object}.
254   * 
255   * @param object
256   *          The ID of the object to delete.
257   * @param parameters
258   *          URL parameters to include in the API call.
259   * @return {@code true} if Facebook indicated that the object was successfully deleted, {@code false} otherwise.
260   * @throws FacebookException
261   *           If an error occurred while attempting to delete the object.
262   */
263  boolean deleteObject(String object, Parameter... parameters);
264
265  /**
266   * Converts an arbitrary number of {@code sessionKeys} to OAuth access tokens.
267   * <p>
268   * See the <a href="http://developers.facebook.com/docs/guides/upgrade">Facebook Platform Upgrade Guide</a> for
269   * details on how this process works and why you should convert your application's session keys if you haven't
270   * already.
271   * 
272   * @param appId
273   *          A Facebook application ID.
274   * @param secretKey
275   *          A Facebook application secret key.
276   * @param sessionKeys
277   *          The Old REST API session keys to be converted to OAuth access tokens.
278   * @return A list of access tokens ordered to correspond to the {@code sessionKeys} argument list.
279   * @throws FacebookException
280   *           If an error occurs while attempting to convert the session keys to API keys.
281   * @since 1.6
282   */
283  List<AccessToken> convertSessionKeysToAccessTokens(String appId, String secretKey, String... sessionKeys);
284
285  /**
286   * Obtains an access token which can be used to perform Graph API operations on behalf of a user.
287   * <p>
288   * See <a href="https://developers.facebook.com/docs/facebook-login/access-tokens">Access Tokens</a>.
289   *
290   * @param appId
291   *          The ID of the app for which you'd like to obtain an access token.
292   * @param appSecret
293   *          The secret for the app for which you'd like to obtain an access token.
294   * @param redirectUri
295   *          The redirect URI which was used to obtain the {@code verificationCode}.
296   * @param verificationCode
297   *          The verification code in the Graph API callback to the redirect URI.
298   * @return The access token for the user identified by {@code appId}, {@code appSecret}, {@code redirectUri} and
299   *         {@code verificationCode}.
300   * @throws FacebookException
301   *           If an error occurs while attempting to obtain an access token.
302   * @since 1.8.0
303   */
304  AccessToken obtainUserAccessToken(String appId, String appSecret, String redirectUri, String verificationCode);
305
306  /**
307   * Obtains an access token which can be used to perform Graph API operations on behalf of an application instead of a
308   * user.
309   * <p>
310   * See <a href="https://developers.facebook.com/docs/authentication/applications/" >Facebook's authenticating as an
311   * app documentation</a>.
312   * 
313   * @param appId
314   *          The ID of the app for which you'd like to obtain an access token.
315   * @param appSecret
316   *          The secret for the app for which you'd like to obtain an access token.
317   * @return The access token for the application identified by {@code appId} and {@code appSecret}.
318   * @throws FacebookException
319   *           If an error occurs while attempting to obtain an access token.
320   * @since 1.6.10
321   */
322  AccessToken obtainAppAccessToken(String appId, String appSecret);
323
324  /**
325   * Obtains an extended access token for the given existing, non-expired, short-lived access_token.
326   * <p>
327   * See <a href="https://developers.facebook.com/roadmap/offline-access-removal/#extend_token">Facebook's extend access
328   * token documentation</a>.
329   * 
330   * @param appId
331   *          The ID of the app for which you'd like to obtain an extended access token.
332   * @param appSecret
333   *          The secret for the app for which you'd like to obtain an extended access token.
334   * @param accessToken
335   *          The non-expired, short-lived access token to extend.
336   * @return An extended access token for the given {@code accessToken}.
337   * @throws FacebookException
338   *           If an error occurs while attempting to obtain an extended access token.
339   * @since 1.6.10
340   */
341  AccessToken obtainExtendedAccessToken(String appId, String appSecret, String accessToken);
342
343  /**
344   * Generates an {@code appsecret_proof} value.
345   * <p>
346   * See <a href="https://developers.facebook.com/docs/graph-api/securing-requests">Facebook's 'securing requests'
347   * documentation</a> for more info.
348   * 
349   * @param accessToken
350   *          The access token required to generate the {@code appsecret_proof} value.
351   * @param appSecret
352   *          The secret for the app for which you'd like to generate the {@code appsecret_proof} value.
353   * @return A hex-encoded SHA256 hash as a {@code String}.
354   * @throws IllegalStateException
355   *           If creating the {@code appsecret_proof} fails.
356   * @since 1.6.13
357   */
358  String obtainAppSecretProof(String accessToken, String appSecret);
359
360  /**
361   * Convenience method which invokes {@link #obtainExtendedAccessToken(String, String, String)} with the current access
362   * token.
363   * 
364   * @param appId
365   *          The ID of the app for which you'd like to obtain an extended access token.
366   * @param appSecret
367   *          The secret for the app for which you'd like to obtain an extended access token.
368   * @return An extended access token for the given {@code accessToken}.
369   * @throws FacebookException
370   *           If an error occurs while attempting to obtain an extended access token.
371   * @throws IllegalStateException
372   *           If this instance was not constructed with an access token.
373   * @since 1.6.10
374   */
375  AccessToken obtainExtendedAccessToken(String appId, String appSecret);
376
377  /**
378   * Parses a signed request and verifies it against your App Secret.
379   * <p>
380   * See <a href="http://developers.facebook.com/docs/howtos/login/signed-request/">Facebook's signed request
381   * documentation</a>.
382   * 
383   * @param signedRequest
384   *          The signed request to parse.
385   * @param appSecret
386   *          The secret for the app that can read this signed request.
387   * @param objectType
388   *          Object type token.
389   * @param <T>
390   *          class of objectType
391   * @return An instance of type {@code objectType} which contains the decoded object embedded within
392   *         {@code signedRequest}.
393   * @throws FacebookSignedRequestParsingException
394   *           If an error occurs while trying to process {@code signedRequest}.
395   * @throws FacebookSignedRequestVerificationException
396   *           If {@code signedRequest} fails verification against {@code appSecret}.
397   * @since 1.6.13
398   */
399  <T> T parseSignedRequest(String signedRequest, String appSecret, Class<T> objectType);
400
401  /**
402   * Method to initialize the device access token generation.
403   *
404   * You receive a {@link DeviceCode} instance and have to show the user the {@link DeviceCode#getVerificationUri()} and
405   * the {@link DeviceCode#getUserCode()}. The user have to enter the user code at the verification url.
406   *
407   * Save the {@link DeviceCode#getCode()} to use it later, when polling Facebook with the
408   * {@link #obtainDeviceAccessToken(java.lang.String)} method.
409   *
410   * @param scope
411   *          List of Permissions to request from the person using your app.
412   * @return Instance of {@code DeviceCode} including the information to obtain the Device access token
413   */
414  DeviceCode fetchDeviceCode(ScopeBuilder scope);
415
416  /**
417   * Method to poll Facebook and fetch the Device Access Token.
418   *
419   * You have to use this method to check if the user confirms the authorization.
420   *
421   * {@link FacebookOAuthException} can be thrown if the authorization is declined or still pending.
422   *
423   * @param code
424   *          The device
425   * @return An extended access token for the given {@link AccessToken}.
426   * @throws com.restfb.exception.devicetoken.FacebookDeviceTokenCodeExpiredException
427   *           the {@link DeviceCode#getCode()} is expired, please fetch a new {@link DeviceCode}.
428   * @throws com.restfb.exception.devicetoken.FacebookDeviceTokenPendingException
429   *           the user has not finished the authorisation process, yet. Please poll again later.
430   * @throws com.restfb.exception.devicetoken.FacebookDeviceTokenDeclinedException
431   *           the user declined the authorisation. You have to handle this problem.
432   * @throws com.restfb.exception.devicetoken.FacebookDeviceTokenSlowdownException
433   *           you tried too often to fetch the device access token. You have to use a larger interval
434   * @since 1.12.0
435   */
436  AccessToken obtainDeviceAccessToken(String code) throws FacebookDeviceTokenCodeExpiredException,
437      FacebookDeviceTokenPendingException, FacebookDeviceTokenDeclinedException, FacebookDeviceTokenSlowdownException;
438
439  /**
440   * <p>
441   * When working with access tokens, you may need to check what information is associated with them, such as its user
442   * or expiry. To get this information you can use the debug tool in the developer site, or you can use this function.
443   * </p>
444   * 
445   * <p>
446   * You must instantiate your FacebookClient using your App Access Token, or a valid User Access Token from a developer
447   * of the app.
448   * </p>
449   * 
450   * <p>
451   * Note that if your app is set to Native/Desktop in the Advanced settings of your App Dashboard, the underlying
452   * GraphAPI endpoint will not work with your app token unless you change the "App Secret in Client" setting to NO. If
453   * you do not see this setting, make sure your "App Type" is set to Native/Desktop and then press the save button at
454   * the bottom of the page. This will not affect apps set to Web.
455   * </p>
456   * 
457   * <p>
458   * The response of the API call is a JSON array containing data and a map of fields. For example:
459   * </p>
460   * 
461   * <pre>
462   * {@code
463   * {
464   *     "data": {
465   *         "app_id": 138483919580948, 
466   *         "application": "Social Cafe", 
467   *         "expires_at": 1352419328, 
468   *         "is_valid": true, 
469   *         "issued_at": 1347235328, 
470   *         "metadata": {
471   *             "sso": "iphone-safari"
472   *         }, 
473   *         "scopes": [
474   *             "email", 
475   *             "publish_actions"
476   *         ], 
477   *         "user_id": 1207059
478   *     }
479   * }
480   * }
481   * </pre>
482   * 
483   * <p>
484   * Note that the {@code issued_at} field is not returned for short-lived access tokens.
485   * </p>
486   * 
487   * <p>
488   * See <a href="https://developers.facebook.com/docs/howtos/login/debugging-access-tokens/"> Debugging an Access
489   * Token</a>
490   * </p>
491   * 
492   * @param inputToken
493   *          The Access Token to debug.
494   * 
495   * @return A JsonObject containing the debug information for the accessToken.
496   * @since 1.6.13
497   */
498  DebugTokenInfo debugToken(String inputToken);
499
500  /**
501   * Gets the {@code JsonMapper} used to convert Facebook JSON to Java objects.
502   * 
503   * @return The {@code JsonMapper} used to convert Facebook JSON to Java objects.
504   * @since 1.6.7
505   */
506  JsonMapper getJsonMapper();
507
508  /**
509   * Gets the {@code WebRequestor} used to talk to the Facebook API endpoints.
510   * 
511   * @return The {@code WebRequestor} used to talk to the Facebook API endpoints.
512   * @since 1.6.7
513   */
514  WebRequestor getWebRequestor();
515
516  /**
517   * generates an logout url
518   * 
519   * @param next
520   *          may be null, url the webpage should redirect after logout
521   * @return the logout url
522   * @since 1.9.0
523   */
524  String getLogoutUrl(String next);
525
526  /**
527   * generates the login dialog url
528   * 
529   * @param appId
530   *          The ID of your app, found in your app's dashboard.
531   * @param redirectUri
532   *          The URL that you want to redirect the person logging in back to. This URL will capture the response from
533   *          the Login Dialog. If you are using this in a webview within a desktop app, this must be set to
534   *          <code>https://www.facebook.com/connect/login_success.html</code>.
535   * @param scope
536   *          List of Permissions to request from the person using your app.
537   * @param additionalParameters
538   *          List of additional parameters
539   * @since 1.9.0
540   * @return the login dialog url
541   */
542  String getLoginDialogUrl(String appId, String redirectUri, ScopeBuilder scope, Parameter... additionalParameters);
543
544  /**
545   * Represents an access token/expiration date pair.
546   * <p>
547   * Facebook returns these types when performing access token-related operations - see
548   * {@link com.restfb.FacebookClient#convertSessionKeysToAccessTokens(String, String, String...)},
549   * {@link com.restfb.FacebookClient#obtainAppAccessToken(String, String)}, and
550   * {@link com.restfb.FacebookClient#obtainExtendedAccessToken(String, String, String)} for details.
551   * 
552   * @author <a href="http://restfb.com">Mark Allen</a>
553   */
554  class AccessToken {
555    @Facebook("access_token")
556    private String accessToken;
557
558    @Facebook("expires_in")
559    private Long rawExpires;
560
561    private Long expires;
562
563    @Facebook("token_type")
564    private String tokenType;
565
566    /**
567     * Given a query string of the form {@code access_token=XXX} or {@code access_token=XXX&expires=YYY}, return an
568     * {@code AccessToken} instance.
569     * <p>
570     * The {@code queryString} is required to contain an {@code access_token} parameter with a non-{@code null} value.
571     * The {@code expires} value is optional and should be the number of seconds since the epoch. If the {@code expires}
572     * value cannot be parsed, the returned {@code AccessToken} will have a {@code null} {@code expires} value.
573     * 
574     * @param queryString
575     *          The Facebook query string out of which to parse an {@code AccessToken} instance.
576     * @return An {@code AccessToken} instance which corresponds to the given {@code queryString}.
577     * @throws IllegalArgumentException
578     *           If no {@code access_token} parameter is present in the query string.
579     * @since 1.6.10
580     */
581    public static AccessToken fromQueryString(String queryString) {
582      // Query string can be of the form 'access_token=XXX' or
583      // 'access_token=XXX&expires=YYY'
584      Map<String, List<String>> urlParameters = extractParametersFromQueryString(queryString);
585
586      String extendedAccessToken = null;
587      String tokenType = null;
588
589      if (urlParameters.containsKey("access_token")) {
590        extendedAccessToken = urlParameters.get("access_token").get(0);
591      }
592
593      if (urlParameters.containsKey("token_type")) {
594        tokenType = urlParameters.get("token_type").get(0);
595      }
596
597      if (extendedAccessToken == null) {
598        throw new IllegalArgumentException(format(
599          "Was expecting a query string of the form 'access_token=XXX' or 'access_token=XXX&expires=YYY'. Instead, the query string was '%s'",
600          queryString));
601      }
602
603      Long expires = null;
604
605      // If an expires or expires_in value was provided and it's a valid long, great - use it.
606      // Otherwise ignore it.
607      String rawExpires = null;
608
609      if (urlParameters.containsKey("expires")) {
610        rawExpires = urlParameters.get("expires").get(0);
611      }
612
613      if (urlParameters.containsKey("expires_in")) {
614        rawExpires = urlParameters.get("expires_in").get(0);
615      }
616
617      if (rawExpires != null) {
618        try {
619          expires = Long.valueOf(rawExpires);
620        } catch (NumberFormatException e) {
621          // rawExpires is not a number, NumberFormatException ignored
622        }
623        if (expires != null) {
624          expires = new Date().getTime() + 1000L * expires;
625        }
626      }
627
628      AccessToken accessToken = new AccessToken();
629      accessToken.accessToken = extendedAccessToken;
630      accessToken.expires = expires;
631      accessToken.tokenType = tokenType;
632      return accessToken;
633    }
634
635    @Override
636    public int hashCode() {
637      return ReflectionUtils.hashCode(this);
638    }
639
640    @Override
641    public boolean equals(Object that) {
642      return ReflectionUtils.equals(this, that);
643    }
644
645    @Override
646    public String toString() {
647      return ReflectionUtils.toString(this);
648    }
649
650    /**
651     * The access token's value.
652     * 
653     * @return The access token's value.
654     */
655    public String getAccessToken() {
656      return accessToken;
657    }
658
659    /**
660     * The date on which the access token expires.
661     * 
662     * @return The date on which the access token expires.
663     */
664    public Date getExpires() {
665      return expires == null ? null : new Date(expires);
666    }
667
668    /**
669     * The token type of this access token provided by Facebook
670     * 
671     * @return the access token type
672     */
673    public String getTokenType() {
674      return tokenType;
675    }
676
677    @JsonMapper.JsonMappingCompleted
678    void convertExpires() {
679      if (rawExpires != null) {
680        expires = new Date().getTime() + 1000L * rawExpires;
681      }
682    }
683
684  }
685
686  /**
687   * <p>
688   * Represents the result of a {@link FacebookClient#debugToken(String)} inquiry.
689   * </p>
690   * 
691   * FIXME does this class belong here?
692   * 
693   * <p>
694   * See <a href="https://developers.facebook.com/docs/howtos/login/debugging-access-tokens/">Debug access tokens</a>
695   * 
696   * @author Broc Seib
697   */
698  class DebugTokenInfo extends AbstractFacebookType {
699
700    private static final long serialVersionUID = 1L;
701
702    /**
703     * The ID of the application this access token is for.
704     */
705    @Facebook("app_id")
706    private String appId;
707
708    /**
709     * Name of the application this access token is for.
710     */
711    @Facebook
712    private String application;
713
714    /**
715     * Timestamp when this access token expires.
716     */
717    @Facebook("expires_at")
718    private Date expiresAt;
719
720    /**
721     * Timestamp when app's access to user data expires.
722     */
723    @Facebook("data_access_expires_at")
724    private Date dataAccessExpiresAt;
725
726    /**
727     * Timestamp when this access token was issued.
728     */
729    @Facebook("issued_at")
730    private Date issuedAt;
731
732    /**
733     * Whether the access token is still valid or not.
734     */
735    @Facebook("is_valid")
736    private Boolean isValid;
737
738    /**
739     * The ID of the user this access token is for.
740     */
741    @Facebook("user_id")
742    private String userId;
743
744    /**
745     * For impersonated access tokens, the ID of the page this token contains.
746     */
747    @Facebook("profile_id")
748    private String profileId;
749
750    /**
751     * General metadata associated with the access token. Can contain data like 'sso', 'auth_type', 'auth_nonce'
752     */
753    @Facebook
754    private JsonObject metadata;
755
756    /**
757     * Any error that a request to the graph api would return due to the access token.
758     */
759    @Facebook
760    private DebugTokenError error;
761
762    /**
763     * List of permissions that the user has granted for the app in this access token.
764     */
765    @Facebook
766    private List<String> scopes = new ArrayList<>();
767
768    @Facebook
769    private String type;
770
771    /**
772     * The application id.
773     * 
774     * @return The id of the application.
775     */
776    public String getAppId() {
777      return appId;
778    }
779
780    /**
781     * The application name.
782     * 
783     * @return The name of the application.
784     */
785    public String getApplication() {
786      return application;
787    }
788
789    /**
790     * The date on which the access token expires.
791     * 
792     * @return The date on which the access token expires.
793     */
794    public Date getExpiresAt() {
795      return expiresAt;
796    }
797
798    /**
799     * Timestamp when app's access to user data expires.
800     *
801     * @return The date when app's access to user data expires.
802     */
803    public Date getDataAccessExpiresAt() {
804      return dataAccessExpiresAt;
805    }
806
807    /**
808     * The date on which the access token was issued.
809     * 
810     * @return The date on which the access token was issued.
811     */
812    public Date getIssuedAt() {
813      return issuedAt;
814    }
815
816    /**
817     * Whether or not the token is valid.
818     * 
819     * @return Whether or not the token is valid.
820     */
821    public Boolean isValid() {
822      return isValid;
823    }
824
825    /**
826     * The user id.
827     * 
828     * @return The user id.
829     */
830    public String getUserId() {
831      return userId;
832    }
833
834    /**
835     * List of scopes the access token 'contains'
836     * 
837     * @return list of scopes
838     */
839    public List<String> getScopes() {
840      return unmodifiableList(scopes);
841    }
842
843    /**
844     * General metadata associated with the access token. Can contain data like 'sso', 'auth_type', 'auth_nonce'
845     * 
846     * @return General metadata associated with the access token
847     */
848    public JsonObject getMetaData() {
849      return metadata;
850    }
851
852    /**
853     * All Error data associated with access token debug.
854     * 
855     * @return debug token error
856     */
857    public DebugTokenError getDebugTokenError() {
858      return error;
859    }
860
861    public String getType() {
862      return type;
863    }
864  }
865
866  class DebugTokenError extends AbstractFacebookType {
867
868    private static final long serialVersionUID = 1L;
869
870    /**
871     * The error code for the error.
872     */
873    @Facebook
874    private Integer code;
875
876    /**
877     * The error message for the error.
878     */
879    @Facebook
880    private String message;
881
882    /**
883     * The error subcode for the error.
884     */
885    @Facebook
886    private Integer subcode;
887
888    /**
889     * The error code for the error.
890     * 
891     * @return The error code for the error.
892     */
893    public Integer getCode() {
894      return code;
895    }
896
897    /**
898     * The error message for the error.
899     * 
900     * @return The error message for the error.
901     */
902    public String getMessage() {
903      return message;
904    }
905
906    /**
907     * The error subcode for the error.
908     * 
909     * @return The error subcode for the error.
910     */
911    public Integer getSubcode() {
912      return subcode;
913    }
914
915  }
916}