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