001/*
002 * Copyright (c) 2010-2021 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#deleting">Graph API delete</a> operation on the given
261   * {@code object}.
262   * 
263   * @param object
264   *          The ID of the object to delete.
265   * @param parameters
266   *          URL parameters to include in the API call.
267   * @return {@code true} if Facebook indicated that the object was successfully deleted, {@code false} otherwise.
268   * @throws FacebookException
269   *           If an error occurred while attempting to delete the object.
270   */
271  boolean deleteObject(String object, Parameter... parameters);
272
273  /**
274   * Converts an arbitrary number of {@code sessionKeys} to OAuth access tokens.
275   * <p>
276   * See the <a href="http://developers.facebook.com/docs/guides/upgrade">Facebook Platform Upgrade Guide</a> for
277   * details on how this process works and why you should convert your application's session keys if you haven't
278   * already.
279   * 
280   * @param appId
281   *          A Facebook application ID.
282   * @param secretKey
283   *          A Facebook application secret key.
284   * @param sessionKeys
285   *          The Old REST API session keys to be converted to OAuth access tokens.
286   * @return A list of access tokens ordered to correspond to the {@code sessionKeys} argument list.
287   * @throws FacebookException
288   *           If an error occurs while attempting to convert the session keys to API keys.
289   * @since 1.6
290   */
291  List<AccessToken> convertSessionKeysToAccessTokens(String appId, String secretKey, String... sessionKeys);
292
293  /**
294   * Obtains an access token which can be used to perform Graph API operations on behalf of a user.
295   * <p>
296   * See <a href="https://developers.facebook.com/docs/facebook-login/access-tokens">Access Tokens</a>.
297   *
298   * @param appId
299   *          The ID of the app for which you'd like to obtain an access token.
300   * @param appSecret
301   *          The secret for the app for which you'd like to obtain an access token.
302   * @param redirectUri
303   *          The redirect URI which was used to obtain the {@code verificationCode}.
304   * @param verificationCode
305   *          The verification code in the Graph API callback to the redirect URI.
306   * @return The access token for the user identified by {@code appId}, {@code appSecret}, {@code redirectUri} and
307   *         {@code verificationCode}.
308   * @throws FacebookException
309   *           If an error occurs while attempting to obtain an access token.
310   * @since 1.8.0
311   */
312  AccessToken obtainUserAccessToken(String appId, String appSecret, String redirectUri, String verificationCode);
313
314  /**
315   * Obtains an access token which can be used to perform Graph API operations on behalf of an application instead of a
316   * user.
317   * <p>
318   * See <a href="https://developers.facebook.com/docs/authentication/applications/" >Facebook's authenticating as an
319   * app documentation</a>.
320   * 
321   * @param appId
322   *          The ID of the app for which you'd like to obtain an access token.
323   * @param appSecret
324   *          The secret for the app for which you'd like to obtain an access token.
325   * @return The access token for the application identified by {@code appId} and {@code appSecret}.
326   * @throws FacebookException
327   *           If an error occurs while attempting to obtain an access token.
328   * @since 1.6.10
329   */
330  AccessToken obtainAppAccessToken(String appId, String appSecret);
331
332  /**
333   * Obtains an extended access token for the given existing, non-expired, short-lived access_token.
334   * <p>
335   * See <a href="https://developers.facebook.com/roadmap/offline-access-removal/#extend_token">Facebook's extend access
336   * token documentation</a>.
337   * 
338   * @param appId
339   *          The ID of the app for which you'd like to obtain an extended access token.
340   * @param appSecret
341   *          The secret for the app for which you'd like to obtain an extended access token.
342   * @param accessToken
343   *          The non-expired, short-lived access token to extend.
344   * @return An extended access token for the given {@code accessToken}.
345   * @throws FacebookException
346   *           If an error occurs while attempting to obtain an extended access token.
347   * @since 1.6.10
348   */
349  AccessToken obtainExtendedAccessToken(String appId, String appSecret, String accessToken);
350
351  /**
352   * Generates an {@code appsecret_proof} value.
353   * <p>
354   * See <a href="https://developers.facebook.com/docs/graph-api/securing-requests">Facebook's 'securing requests'
355   * documentation</a> for more info.
356   * 
357   * @param accessToken
358   *          The access token required to generate the {@code appsecret_proof} value.
359   * @param appSecret
360   *          The secret for the app for which you'd like to generate the {@code appsecret_proof} value.
361   * @return A hex-encoded SHA256 hash as a {@code String}.
362   * @throws IllegalStateException
363   *           If creating the {@code appsecret_proof} fails.
364   * @since 1.6.13
365   */
366  String obtainAppSecretProof(String accessToken, String appSecret);
367
368  /**
369   * Convenience method which invokes {@link #obtainExtendedAccessToken(String, String, String)} with the current access
370   * token.
371   * 
372   * @param appId
373   *          The ID of the app for which you'd like to obtain an extended access token.
374   * @param appSecret
375   *          The secret for the app for which you'd like to obtain an extended access token.
376   * @return An extended access token for the given {@code accessToken}.
377   * @throws FacebookException
378   *           If an error occurs while attempting to obtain an extended access token.
379   * @throws IllegalStateException
380   *           If this instance was not constructed with an access token.
381   * @since 1.6.10
382   */
383  AccessToken obtainExtendedAccessToken(String appId, String appSecret);
384
385  /**
386   * Parses a signed request and verifies it against your App Secret.
387   * <p>
388   * See <a href="http://developers.facebook.com/docs/howtos/login/signed-request/">Facebook's signed request
389   * documentation</a>.
390   * 
391   * @param signedRequest
392   *          The signed request to parse.
393   * @param appSecret
394   *          The secret for the app that can read this signed request.
395   * @param objectType
396   *          Object type token.
397   * @param <T>
398   *          class of objectType
399   * @return An instance of type {@code objectType} which contains the decoded object embedded within
400   *         {@code signedRequest}.
401   * @throws FacebookSignedRequestParsingException
402   *           If an error occurs while trying to process {@code signedRequest}.
403   * @throws FacebookSignedRequestVerificationException
404   *           If {@code signedRequest} fails verification against {@code appSecret}.
405   * @since 1.6.13
406   */
407  <T> T parseSignedRequest(String signedRequest, String appSecret, Class<T> objectType);
408
409  /**
410   * Method to initialize the device access token generation.
411   *
412   * You receive a {@link DeviceCode} instance and have to show the user the {@link DeviceCode#getVerificationUri()} and
413   * the {@link DeviceCode#getUserCode()}. The user have to enter the user code at the verification url.
414   *
415   * Save the {@link DeviceCode#getCode()} to use it later, when polling Facebook with the
416   * {@link #obtainDeviceAccessToken(java.lang.String)} method.
417   *
418   * @param scope
419   *          List of Permissions to request from the person using your app.
420   * @return Instance of {@code DeviceCode} including the information to obtain the Device access token
421   */
422  DeviceCode fetchDeviceCode(ScopeBuilder scope);
423
424  /**
425   * Method to poll Facebook and fetch the Device Access Token.
426   *
427   * You have to use this method to check if the user confirms the authorization.
428   *
429   * {@link FacebookOAuthException} can be thrown if the authorization is declined or still pending.
430   *
431   * @param code
432   *          The device
433   * @return An extended access token for the given {@link AccessToken}.
434   * @throws com.restfb.exception.devicetoken.FacebookDeviceTokenCodeExpiredException
435   *           the {@link DeviceCode#getCode()} is expired, please fetch a new {@link DeviceCode}.
436   * @throws com.restfb.exception.devicetoken.FacebookDeviceTokenPendingException
437   *           the user has not finished the authorisation process, yet. Please poll again later.
438   * @throws com.restfb.exception.devicetoken.FacebookDeviceTokenDeclinedException
439   *           the user declined the authorisation. You have to handle this problem.
440   * @throws com.restfb.exception.devicetoken.FacebookDeviceTokenSlowdownException
441   *           you tried too often to fetch the device access token. You have to use a larger interval
442   * @since 1.12.0
443   */
444  AccessToken obtainDeviceAccessToken(String code) throws FacebookDeviceTokenCodeExpiredException,
445      FacebookDeviceTokenPendingException, FacebookDeviceTokenDeclinedException, FacebookDeviceTokenSlowdownException;
446
447  /**
448   * <p>
449   * When working with access tokens, you may need to check what information is associated with them, such as its user
450   * or expiry. To get this information you can use the debug tool in the developer site, or you can use this function.
451   * </p>
452   * 
453   * <p>
454   * You must instantiate your FacebookClient using your App Access Token, or a valid User Access Token from a developer
455   * of the app.
456   * </p>
457   * 
458   * <p>
459   * Note that if your app is set to Native/Desktop in the Advanced settings of your App Dashboard, the underlying
460   * GraphAPI endpoint will not work with your app token unless you change the "App Secret in Client" setting to NO. If
461   * you do not see this setting, make sure your "App Type" is set to Native/Desktop and then press the save button at
462   * the bottom of the page. This will not affect apps set to Web.
463   * </p>
464   * 
465   * <p>
466   * The response of the API call is a JSON array containing data and a map of fields. For example:
467   * </p>
468   * 
469   * <pre>
470   * {@code
471   * {
472   *     "data": {
473   *         "app_id": 138483919580948, 
474   *         "application": "Social Cafe", 
475   *         "expires_at": 1352419328, 
476   *         "is_valid": true, 
477   *         "issued_at": 1347235328, 
478   *         "metadata": {
479   *             "sso": "iphone-safari"
480   *         }, 
481   *         "scopes": [
482   *             "email", 
483   *             "publish_actions"
484   *         ], 
485   *         "user_id": 1207059
486   *     }
487   * }
488   * }
489   * </pre>
490   * 
491   * <p>
492   * Note that the {@code issued_at} field is not returned for short-lived access tokens.
493   * </p>
494   * 
495   * <p>
496   * See <a href="https://developers.facebook.com/docs/howtos/login/debugging-access-tokens/"> Debugging an Access
497   * Token</a>
498   * </p>
499   * 
500   * @param inputToken
501   *          The Access Token to debug.
502   * 
503   * @return A JsonObject containing the debug information for the accessToken.
504   * @since 1.6.13
505   */
506  DebugTokenInfo debugToken(String inputToken);
507
508  /**
509   * Gets the {@code JsonMapper} used to convert Facebook JSON to Java objects.
510   * 
511   * @return The {@code JsonMapper} used to convert Facebook JSON to Java objects.
512   * @since 1.6.7
513   */
514  JsonMapper getJsonMapper();
515
516  /**
517   * Gets the {@code WebRequestor} used to talk to the Facebook API endpoints.
518   * 
519   * @return The {@code WebRequestor} used to talk to the Facebook API endpoints.
520   * @since 1.6.7
521   */
522  WebRequestor getWebRequestor();
523
524  /**
525   * generates an logout url
526   * 
527   * @param next
528   *          may be null, url the webpage should redirect after logout
529   * @return the logout url
530   * @since 1.9.0
531   */
532  String getLogoutUrl(String next);
533
534  /**
535   * generates the login dialog url
536   * 
537   * @param appId
538   *          The ID of your app, found in your app's dashboard.
539   * @param redirectUri
540   *          The URL that you want to redirect the person logging in back to. This URL will capture the response from
541   *          the Login Dialog. If you are using this in a webview within a desktop app, this must be set to
542   *          <code>https://www.facebook.com/connect/login_success.html</code>.
543   * @param scope
544   *          List of Permissions to request from the person using your app.
545   * @param additionalParameters
546   *          List of additional parameters
547   * @since 1.9.0
548   * @return the login dialog url
549   */
550  String getLoginDialogUrl(String appId, String redirectUri, ScopeBuilder scope, Parameter... additionalParameters);
551
552  /**
553   * Represents an access token/expiration date pair.
554   * <p>
555   * Facebook returns these types when performing access token-related operations - see
556   * {@link com.restfb.FacebookClient#convertSessionKeysToAccessTokens(String, String, String...)},
557   * {@link com.restfb.FacebookClient#obtainAppAccessToken(String, String)}, and
558   * {@link com.restfb.FacebookClient#obtainExtendedAccessToken(String, String, String)} for details.
559   * 
560   * @author <a href="http://restfb.com">Mark Allen</a>
561   */
562  class AccessToken {
563    @Facebook("access_token")
564    private String accessToken;
565
566    @Facebook("expires_in")
567    private Long rawExpires;
568
569    private Long expires;
570
571    @Facebook("token_type")
572    private String tokenType;
573
574    private FacebookClient client;
575
576    public void setClient(FacebookClient client) {
577      this.client = client;
578    }
579
580    public FacebookClient getClient() {
581      return Optional.ofNullable(client).orElse(null);
582    }
583
584    /**
585     * Given a query string of the form {@code access_token=XXX} or {@code access_token=XXX&expires=YYY}, return an
586     * {@code AccessToken} instance.
587     * <p>
588     * The {@code queryString} is required to contain an {@code access_token} parameter with a non-{@code null} value.
589     * The {@code expires} value is optional and should be the number of seconds since the epoch. If the {@code expires}
590     * value cannot be parsed, the returned {@code AccessToken} will have a {@code null} {@code expires} value.
591     * 
592     * @param queryString
593     *          The Facebook query string out of which to parse an {@code AccessToken} instance.
594     * @return An {@code AccessToken} instance which corresponds to the given {@code queryString}.
595     * @throws IllegalArgumentException
596     *           If no {@code access_token} parameter is present in the query string.
597     * @since 1.6.10
598     */
599    public static AccessToken fromQueryString(String queryString) {
600      // Query string can be of the form 'access_token=XXX' or
601      // 'access_token=XXX&expires=YYY'
602      Map<String, List<String>> urlParameters = extractParametersFromQueryString(queryString);
603
604      String extendedAccessToken = null;
605      String tokenType = null;
606
607      if (urlParameters.containsKey("access_token")) {
608        extendedAccessToken = urlParameters.get("access_token").get(0);
609      }
610
611      if (urlParameters.containsKey("token_type")) {
612        tokenType = urlParameters.get("token_type").get(0);
613      }
614
615      if (extendedAccessToken == null) {
616        throw new IllegalArgumentException(format(
617          "Was expecting a query string of the form 'access_token=XXX' or 'access_token=XXX&expires=YYY'. Instead, the query string was '%s'",
618          queryString));
619      }
620
621      Long expires = null;
622
623      // If an expires or expires_in value was provided and it's a valid long, great - use it.
624      // Otherwise ignore it.
625      String rawExpires = null;
626
627      if (urlParameters.containsKey("expires")) {
628        rawExpires = urlParameters.get("expires").get(0);
629      }
630
631      if (urlParameters.containsKey("expires_in")) {
632        rawExpires = urlParameters.get("expires_in").get(0);
633      }
634
635      if (rawExpires != null && rawExpires.trim().matches("\\d+")) {
636        expires = new Date().getTime() + Long.parseLong(rawExpires) * 1000L;
637      }
638
639      AccessToken accessToken = new AccessToken();
640      accessToken.accessToken = extendedAccessToken;
641      accessToken.expires = expires;
642      accessToken.tokenType = tokenType;
643      return accessToken;
644    }
645
646    @Override
647    public int hashCode() {
648      return ReflectionUtils.hashCode(this);
649    }
650
651    @Override
652    public boolean equals(Object that) {
653      return ReflectionUtils.equals(this, that);
654    }
655
656    @Override
657    public String toString() {
658      return ReflectionUtils.toString(this);
659    }
660
661    /**
662     * The access token's value.
663     * 
664     * @return The access token's value.
665     */
666    public String getAccessToken() {
667      return accessToken;
668    }
669
670    /**
671     * The date on which the access token expires.
672     * 
673     * @return The date on which the access token expires.
674     */
675    public Date getExpires() {
676      return expires == null ? null : new Date(expires);
677    }
678
679    /**
680     * The token type of this access token provided by Facebook
681     * 
682     * @return the access token type
683     */
684    public String getTokenType() {
685      return tokenType;
686    }
687
688    @JsonMapper.JsonMappingCompleted
689    void convertExpires() {
690      if (rawExpires != null) {
691        expires = new Date().getTime() + 1000L * rawExpires;
692      }
693    }
694
695  }
696
697  /**
698   * <p>
699   * Represents the result of a {@link FacebookClient#debugToken(String)} inquiry.
700   * </p>
701   * 
702   * FIXME does this class belong here?
703   * 
704   * <p>
705   * See <a href="https://developers.facebook.com/docs/howtos/login/debugging-access-tokens/">Debug access tokens</a>
706   * 
707   * @author Broc Seib
708   */
709  class DebugTokenInfo extends AbstractFacebookType {
710
711    private static final long serialVersionUID = 1L;
712
713    /**
714     * The ID of the application this access token is for.
715     */
716    @Facebook("app_id")
717    private String appId;
718
719    /**
720     * Name of the application this access token is for.
721     */
722    @Facebook
723    private String application;
724
725    /**
726     * Timestamp when this access token expires.
727     */
728    @Facebook("expires_at")
729    private Date expiresAt;
730
731    /**
732     * Timestamp when app's access to user data expires.
733     */
734    @Facebook("data_access_expires_at")
735    private Date dataAccessExpiresAt;
736
737    /**
738     * Timestamp when this access token was issued.
739     */
740    @Facebook("issued_at")
741    private Date issuedAt;
742
743    /**
744     * Whether the access token is still valid or not.
745     */
746    @Facebook("is_valid")
747    private Boolean isValid;
748
749    /**
750     * The ID of the user this access token is for.
751     */
752    @Facebook("user_id")
753    private String userId;
754
755    /**
756     * For impersonated access tokens, the ID of the page this token contains.
757     */
758    @Facebook("profile_id")
759    private String profileId;
760
761    /**
762     * General metadata associated with the access token. Can contain data like 'sso', 'auth_type', 'auth_nonce'
763     */
764    @Facebook
765    private JsonObject metadata;
766
767    /**
768     * Any error that a request to the graph api would return due to the access token.
769     */
770    @Facebook
771    private DebugTokenError error;
772
773    /**
774     * List of permissions that the user has granted for the app in this access token.
775     */
776    @Facebook
777    private List<String> scopes = new ArrayList<>();
778
779    @Facebook
780    private String type;
781
782    /**
783     * The application id.
784     * 
785     * @return The id of the application.
786     */
787    public String getAppId() {
788      return appId;
789    }
790
791    /**
792     * The application name.
793     * 
794     * @return The name of the application.
795     */
796    public String getApplication() {
797      return application;
798    }
799
800    /**
801     * The date on which the access token expires.
802     * 
803     * @return The date on which the access token expires.
804     */
805    public Date getExpiresAt() {
806      return expiresAt;
807    }
808
809    /**
810     * Timestamp when app's access to user data expires.
811     *
812     * @return The date when app's access to user data expires.
813     */
814    public Date getDataAccessExpiresAt() {
815      return dataAccessExpiresAt;
816    }
817
818    /**
819     * The date on which the access token was issued.
820     * 
821     * @return The date on which the access token was issued.
822     */
823    public Date getIssuedAt() {
824      return issuedAt;
825    }
826
827    /**
828     * Whether or not the token is valid.
829     * 
830     * @return Whether or not the token is valid.
831     */
832    public Boolean isValid() {
833      return isValid;
834    }
835
836    /**
837     * The user id.
838     * 
839     * @return The user id.
840     */
841    public String getUserId() {
842      return userId;
843    }
844
845    /**
846     * List of scopes the access token 'contains'
847     * 
848     * @return list of scopes
849     */
850    public List<String> getScopes() {
851      return unmodifiableList(scopes);
852    }
853
854    /**
855     * General metadata associated with the access token. Can contain data like 'sso', 'auth_type', 'auth_nonce'
856     * 
857     * @return General metadata associated with the access token
858     */
859    public JsonObject getMetaData() {
860      return metadata;
861    }
862
863    /**
864     * All Error data associated with access token debug.
865     * 
866     * @return debug token error
867     */
868    public DebugTokenError getDebugTokenError() {
869      return error;
870    }
871
872    public String getType() {
873      return type;
874    }
875  }
876
877  class DebugTokenError extends AbstractFacebookType {
878
879    private static final long serialVersionUID = 1L;
880
881    /**
882     * The error code for the error.
883     */
884    @Facebook
885    private Integer code;
886
887    /**
888     * The error message for the error.
889     */
890    @Facebook
891    private String message;
892
893    /**
894     * The error subcode for the error.
895     */
896    @Facebook
897    private Integer subcode;
898
899    /**
900     * The error code for the error.
901     * 
902     * @return The error code for the error.
903     */
904    public Integer getCode() {
905      return code;
906    }
907
908    /**
909     * The error message for the error.
910     * 
911     * @return The error message for the error.
912     */
913    public String getMessage() {
914      return message;
915    }
916
917    /**
918     * The error subcode for the error.
919     * 
920     * @return The error subcode for the error.
921     */
922    public Integer getSubcode() {
923      return subcode;
924    }
925
926  }
927}