001/*
002 * Copyright (c) 2010-2020 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) {
636        if (rawExpires.trim().matches("\\d+")) {
637          expires = new Date().getTime() + Long.parseLong(rawExpires) * 1000L;
638        }
639      }
640
641      AccessToken accessToken = new AccessToken();
642      accessToken.accessToken = extendedAccessToken;
643      accessToken.expires = expires;
644      accessToken.tokenType = tokenType;
645      return accessToken;
646    }
647
648    @Override
649    public int hashCode() {
650      return ReflectionUtils.hashCode(this);
651    }
652
653    @Override
654    public boolean equals(Object that) {
655      return ReflectionUtils.equals(this, that);
656    }
657
658    @Override
659    public String toString() {
660      return ReflectionUtils.toString(this);
661    }
662
663    /**
664     * The access token's value.
665     * 
666     * @return The access token's value.
667     */
668    public String getAccessToken() {
669      return accessToken;
670    }
671
672    /**
673     * The date on which the access token expires.
674     * 
675     * @return The date on which the access token expires.
676     */
677    public Date getExpires() {
678      return expires == null ? null : new Date(expires);
679    }
680
681    /**
682     * The token type of this access token provided by Facebook
683     * 
684     * @return the access token type
685     */
686    public String getTokenType() {
687      return tokenType;
688    }
689
690    @JsonMapper.JsonMappingCompleted
691    void convertExpires() {
692      if (rawExpires != null) {
693        expires = new Date().getTime() + 1000L * rawExpires;
694      }
695    }
696
697  }
698
699  /**
700   * <p>
701   * Represents the result of a {@link FacebookClient#debugToken(String)} inquiry.
702   * </p>
703   * 
704   * FIXME does this class belong here?
705   * 
706   * <p>
707   * See <a href="https://developers.facebook.com/docs/howtos/login/debugging-access-tokens/">Debug access tokens</a>
708   * 
709   * @author Broc Seib
710   */
711  class DebugTokenInfo extends AbstractFacebookType {
712
713    private static final long serialVersionUID = 1L;
714
715    /**
716     * The ID of the application this access token is for.
717     */
718    @Facebook("app_id")
719    private String appId;
720
721    /**
722     * Name of the application this access token is for.
723     */
724    @Facebook
725    private String application;
726
727    /**
728     * Timestamp when this access token expires.
729     */
730    @Facebook("expires_at")
731    private Date expiresAt;
732
733    /**
734     * Timestamp when app's access to user data expires.
735     */
736    @Facebook("data_access_expires_at")
737    private Date dataAccessExpiresAt;
738
739    /**
740     * Timestamp when this access token was issued.
741     */
742    @Facebook("issued_at")
743    private Date issuedAt;
744
745    /**
746     * Whether the access token is still valid or not.
747     */
748    @Facebook("is_valid")
749    private Boolean isValid;
750
751    /**
752     * The ID of the user this access token is for.
753     */
754    @Facebook("user_id")
755    private String userId;
756
757    /**
758     * For impersonated access tokens, the ID of the page this token contains.
759     */
760    @Facebook("profile_id")
761    private String profileId;
762
763    /**
764     * General metadata associated with the access token. Can contain data like 'sso', 'auth_type', 'auth_nonce'
765     */
766    @Facebook
767    private JsonObject metadata;
768
769    /**
770     * Any error that a request to the graph api would return due to the access token.
771     */
772    @Facebook
773    private DebugTokenError error;
774
775    /**
776     * List of permissions that the user has granted for the app in this access token.
777     */
778    @Facebook
779    private List<String> scopes = new ArrayList<>();
780
781    @Facebook
782    private String type;
783
784    /**
785     * The application id.
786     * 
787     * @return The id of the application.
788     */
789    public String getAppId() {
790      return appId;
791    }
792
793    /**
794     * The application name.
795     * 
796     * @return The name of the application.
797     */
798    public String getApplication() {
799      return application;
800    }
801
802    /**
803     * The date on which the access token expires.
804     * 
805     * @return The date on which the access token expires.
806     */
807    public Date getExpiresAt() {
808      return expiresAt;
809    }
810
811    /**
812     * Timestamp when app's access to user data expires.
813     *
814     * @return The date when app's access to user data expires.
815     */
816    public Date getDataAccessExpiresAt() {
817      return dataAccessExpiresAt;
818    }
819
820    /**
821     * The date on which the access token was issued.
822     * 
823     * @return The date on which the access token was issued.
824     */
825    public Date getIssuedAt() {
826      return issuedAt;
827    }
828
829    /**
830     * Whether or not the token is valid.
831     * 
832     * @return Whether or not the token is valid.
833     */
834    public Boolean isValid() {
835      return isValid;
836    }
837
838    /**
839     * The user id.
840     * 
841     * @return The user id.
842     */
843    public String getUserId() {
844      return userId;
845    }
846
847    /**
848     * List of scopes the access token 'contains'
849     * 
850     * @return list of scopes
851     */
852    public List<String> getScopes() {
853      return unmodifiableList(scopes);
854    }
855
856    /**
857     * General metadata associated with the access token. Can contain data like 'sso', 'auth_type', 'auth_nonce'
858     * 
859     * @return General metadata associated with the access token
860     */
861    public JsonObject getMetaData() {
862      return metadata;
863    }
864
865    /**
866     * All Error data associated with access token debug.
867     * 
868     * @return debug token error
869     */
870    public DebugTokenError getDebugTokenError() {
871      return error;
872    }
873
874    public String getType() {
875      return type;
876    }
877  }
878
879  class DebugTokenError extends AbstractFacebookType {
880
881    private static final long serialVersionUID = 1L;
882
883    /**
884     * The error code for the error.
885     */
886    @Facebook
887    private Integer code;
888
889    /**
890     * The error message for the error.
891     */
892    @Facebook
893    private String message;
894
895    /**
896     * The error subcode for the error.
897     */
898    @Facebook
899    private Integer subcode;
900
901    /**
902     * The error code for the error.
903     * 
904     * @return The error code for the error.
905     */
906    public Integer getCode() {
907      return code;
908    }
909
910    /**
911     * The error message for the error.
912     * 
913     * @return The error message for the error.
914     */
915    public String getMessage() {
916      return message;
917    }
918
919    /**
920     * The error subcode for the error.
921     * 
922     * @return The error subcode for the error.
923     */
924    public Integer getSubcode() {
925      return subcode;
926    }
927
928  }
929}