001package com.box.sdk; 002 003import java.net.MalformedURLException; 004import java.net.Proxy; 005import java.net.URI; 006import java.net.URL; 007import java.util.ArrayList; 008import java.util.List; 009import java.util.concurrent.locks.ReadWriteLock; 010import java.util.concurrent.locks.ReentrantReadWriteLock; 011 012import com.eclipsesource.json.JsonObject; 013 014/** 015 * Represents an authenticated connection to the Box API. 016 * 017 * <p>This class handles storing authentication information, automatic token refresh, and rate-limiting. It can also be 018 * used to configure the Box API endpoint URL in order to hit a different version of the API. Multiple instances of 019 * BoxAPIConnection may be created to support multi-user login.</p> 020 */ 021public class BoxAPIConnection { 022 /** 023 * The default maximum number of times an API request will be tried when an error occurs. 024 */ 025 public static final int DEFAULT_MAX_ATTEMPTS = 3; 026 027 private static final String AUTHORIZATION_URL = "https://account.box.com/api/oauth2/authorize"; 028 private static final String TOKEN_URL_STRING = "https://api.box.com/oauth2/token"; 029 private static final String DEFAULT_BASE_URL = "https://api.box.com/2.0/"; 030 private static final String DEFAULT_BASE_UPLOAD_URL = "https://upload.box.com/api/2.0/"; 031 032 /** 033 * The amount of buffer time, in milliseconds, to use when determining if an access token should be refreshed. For 034 * example, if REFRESH_EPSILON = 60000 and the access token expires in less than one minute, it will be refreshed. 035 */ 036 private static final long REFRESH_EPSILON = 60000; 037 038 private final String clientID; 039 private final String clientSecret; 040 private final ReadWriteLock refreshLock; 041 042 // These volatile fields are used when determining if the access token needs to be refreshed. Since they are used in 043 // the double-checked lock in getAccessToken(), they must be atomic. 044 private volatile long lastRefresh; 045 private volatile long expires; 046 047 private Proxy proxy; 048 private String proxyUsername; 049 private String proxyPassword; 050 051 private String userAgent; 052 private String accessToken; 053 private String refreshToken; 054 private String tokenURL; 055 private String baseURL; 056 private String baseUploadURL; 057 private boolean autoRefresh; 058 private int maxRequestAttempts; 059 private List<BoxAPIConnectionListener> listeners; 060 private RequestInterceptor interceptor; 061 062 /** 063 * Constructs a new BoxAPIConnection that authenticates with a developer or access token. 064 * @param accessToken a developer or access token to use for authenticating with the API. 065 */ 066 public BoxAPIConnection(String accessToken) { 067 this(null, null, accessToken, null); 068 } 069 070 /** 071 * Constructs a new BoxAPIConnection with an access token that can be refreshed. 072 * @param clientID the client ID to use when refreshing the access token. 073 * @param clientSecret the client secret to use when refreshing the access token. 074 * @param accessToken an initial access token to use for authenticating with the API. 075 * @param refreshToken an initial refresh token to use when refreshing the access token. 076 */ 077 public BoxAPIConnection(String clientID, String clientSecret, String accessToken, String refreshToken) { 078 this.clientID = clientID; 079 this.clientSecret = clientSecret; 080 this.accessToken = accessToken; 081 this.refreshToken = refreshToken; 082 this.tokenURL = TOKEN_URL_STRING; 083 this.baseURL = DEFAULT_BASE_URL; 084 this.baseUploadURL = DEFAULT_BASE_UPLOAD_URL; 085 this.autoRefresh = true; 086 this.maxRequestAttempts = DEFAULT_MAX_ATTEMPTS; 087 this.refreshLock = new ReentrantReadWriteLock(); 088 this.userAgent = "Box Java SDK v2.8.1"; 089 this.listeners = new ArrayList<BoxAPIConnectionListener>(); 090 } 091 092 /** 093 * Constructs a new BoxAPIConnection with an auth code that was obtained from the first half of OAuth. 094 * @param clientID the client ID to use when exchanging the auth code for an access token. 095 * @param clientSecret the client secret to use when exchanging the auth code for an access token. 096 * @param authCode an auth code obtained from the first half of the OAuth process. 097 */ 098 public BoxAPIConnection(String clientID, String clientSecret, String authCode) { 099 this(clientID, clientSecret, null, null); 100 this.authenticate(authCode); 101 } 102 103 /** 104 * Constructs a new BoxAPIConnection. 105 * @param clientID the client ID to use when exchanging the auth code for an access token. 106 * @param clientSecret the client secret to use when exchanging the auth code for an access token. 107 */ 108 public BoxAPIConnection(String clientID, String clientSecret) { 109 this(clientID, clientSecret, null, null); 110 } 111 112 /** 113 * Constructs a new BoxAPIConnection levaraging BoxConfig. 114 * @param boxConfig BoxConfig file, which should have clientId and clientSecret 115 */ 116 public BoxAPIConnection(BoxConfig boxConfig) { 117 this(boxConfig.getClientId(), boxConfig.getClientSecret(), null, null); 118 } 119 120 /** 121 * Restores a BoxAPIConnection from a saved state. 122 * 123 * @see #save 124 * @param clientID the client ID to use with the connection. 125 * @param clientSecret the client secret to use with the connection. 126 * @param state the saved state that was created with {@link #save}. 127 * @return a restored API connection. 128 */ 129 public static BoxAPIConnection restore(String clientID, String clientSecret, String state) { 130 BoxAPIConnection api = new BoxAPIConnection(clientID, clientSecret); 131 api.restore(state); 132 return api; 133 } 134 135 /** 136 * Return the authorization URL which is used to perform the authorization_code based OAuth2 flow. 137 * @param clientID the client ID to use with the connection. 138 * @param redirectUri the URL to which Box redirects the browser when authentication completes. 139 * @param state the text string that you choose. 140 * Box sends the same string to your redirect URL when authentication is complete. 141 * @param scopes this optional parameter identifies the Box scopes available 142 * to the application once it's authenticated. 143 * @return the authorization URL 144 */ 145 public static URL getAuthorizationURL(String clientID, URI redirectUri, String state, List<String> scopes) { 146 URLTemplate template = new URLTemplate(AUTHORIZATION_URL); 147 QueryStringBuilder queryBuilder = new QueryStringBuilder().appendParam("client_id", clientID) 148 .appendParam("response_type", "code") 149 .appendParam("redirect_uri", redirectUri.toString()) 150 .appendParam("state", state); 151 152 if (scopes != null && !scopes.isEmpty()) { 153 StringBuilder builder = new StringBuilder(); 154 int size = scopes.size() - 1; 155 int i = 0; 156 while (i < size) { 157 builder.append(scopes.get(i)); 158 builder.append(" "); 159 i++; 160 } 161 builder.append(scopes.get(i)); 162 163 queryBuilder.appendParam("scope", builder.toString()); 164 } 165 166 return template.buildWithQuery("", queryBuilder.toString()); 167 } 168 169 /** 170 * Authenticates the API connection by obtaining access and refresh tokens using the auth code that was obtained 171 * from the first half of OAuth. 172 * @param authCode the auth code obtained from the first half of the OAuth process. 173 */ 174 public void authenticate(String authCode) { 175 URL url = null; 176 try { 177 url = new URL(this.tokenURL); 178 } catch (MalformedURLException e) { 179 assert false : "An invalid token URL indicates a bug in the SDK."; 180 throw new RuntimeException("An invalid token URL indicates a bug in the SDK.", e); 181 } 182 183 String urlParameters = String.format("grant_type=authorization_code&code=%s&client_id=%s&client_secret=%s", 184 authCode, this.clientID, this.clientSecret); 185 186 BoxAPIRequest request = new BoxAPIRequest(this, url, "POST"); 187 request.shouldAuthenticate(false); 188 request.setBody(urlParameters); 189 190 BoxJSONResponse response = (BoxJSONResponse) request.send(); 191 String json = response.getJSON(); 192 193 JsonObject jsonObject = JsonObject.readFrom(json); 194 this.accessToken = jsonObject.get("access_token").asString(); 195 this.refreshToken = jsonObject.get("refresh_token").asString(); 196 this.lastRefresh = System.currentTimeMillis(); 197 this.expires = jsonObject.get("expires_in").asLong() * 1000; 198 } 199 200 /** 201 * Gets the client ID. 202 * @return the client ID. 203 */ 204 public String getClientID() { 205 return this.clientID; 206 } 207 208 /** 209 * Gets the client secret. 210 * @return the client secret. 211 */ 212 public String getClientSecret() { 213 return this.clientSecret; 214 } 215 216 /** 217 * Sets the amount of time for which this connection's access token is valid before it must be refreshed. 218 * @param milliseconds the number of milliseconds for which the access token is valid. 219 */ 220 public void setExpires(long milliseconds) { 221 this.expires = milliseconds; 222 } 223 224 /** 225 * Gets the amount of time for which this connection's access token is valid. 226 * @return the amount of time in milliseconds. 227 */ 228 public long getExpires() { 229 return this.expires; 230 } 231 232 /** 233 * Gets the token URL that's used to request access tokens. The default value is 234 * "https://www.box.com/api/oauth2/token". 235 * @return the token URL. 236 */ 237 public String getTokenURL() { 238 return this.tokenURL; 239 } 240 241 /** 242 * Sets the token URL that's used to request access tokens. For example, the default token URL is 243 * "https://www.box.com/api/oauth2/token". 244 * @param tokenURL the token URL. 245 */ 246 public void setTokenURL(String tokenURL) { 247 this.tokenURL = tokenURL; 248 } 249 250 /** 251 * Gets the base URL that's used when sending requests to the Box API. The default value is 252 * "https://api.box.com/2.0/". 253 * @return the base URL. 254 */ 255 public String getBaseURL() { 256 return this.baseURL; 257 } 258 259 /** 260 * Sets the base URL to be used when sending requests to the Box API. For example, the default base URL is 261 * "https://api.box.com/2.0/". 262 * @param baseURL a base URL 263 */ 264 public void setBaseURL(String baseURL) { 265 this.baseURL = baseURL; 266 } 267 268 /** 269 * Gets the base upload URL that's used when performing file uploads to Box. 270 * @return the base upload URL. 271 */ 272 public String getBaseUploadURL() { 273 return this.baseUploadURL; 274 } 275 276 /** 277 * Sets the base upload URL to be used when performing file uploads to Box. 278 * @param baseUploadURL a base upload URL. 279 */ 280 public void setBaseUploadURL(String baseUploadURL) { 281 this.baseUploadURL = baseUploadURL; 282 } 283 284 /** 285 * Gets the user agent that's used when sending requests to the Box API. 286 * @return the user agent. 287 */ 288 public String getUserAgent() { 289 return this.userAgent; 290 } 291 292 /** 293 * Sets the user agent to be used when sending requests to the Box API. 294 * @param userAgent the user agent. 295 */ 296 public void setUserAgent(String userAgent) { 297 this.userAgent = userAgent; 298 } 299 300 /** 301 * Gets an access token that can be used to authenticate an API request. This method will automatically refresh the 302 * access token if it has expired since the last call to <code>getAccessToken()</code>. 303 * @return a valid access token that can be used to authenticate an API request. 304 */ 305 public String getAccessToken() { 306 if (this.autoRefresh && this.canRefresh() && this.needsRefresh()) { 307 this.refreshLock.writeLock().lock(); 308 try { 309 if (this.needsRefresh()) { 310 this.refresh(); 311 } 312 } finally { 313 this.refreshLock.writeLock().unlock(); 314 } 315 } 316 317 return this.accessToken; 318 } 319 320 /** 321 * Sets the access token to use when authenticating API requests. 322 * @param accessToken a valid access token to use when authenticating API requests. 323 */ 324 public void setAccessToken(String accessToken) { 325 this.accessToken = accessToken; 326 } 327 328 /** 329 * Gets the refresh lock to be used when refreshing an access token. 330 * @return the refresh lock. 331 */ 332 protected ReadWriteLock getRefreshLock() { 333 return this.refreshLock; 334 } 335 /** 336 * Gets a refresh token that can be used to refresh an access token. 337 * @return a valid refresh token. 338 */ 339 public String getRefreshToken() { 340 return this.refreshToken; 341 } 342 343 /** 344 * Sets the refresh token to use when refreshing an access token. 345 * @param refreshToken a valid refresh token. 346 */ 347 public void setRefreshToken(String refreshToken) { 348 this.refreshToken = refreshToken; 349 } 350 351 /** 352 * Gets the last time that the access token was refreshed. 353 * 354 * @return the last refresh time in milliseconds. 355 */ 356 public long getLastRefresh() { 357 return this.lastRefresh; 358 } 359 360 /** 361 * Sets the last time that the access token was refreshed. 362 * 363 * <p>This value is used when determining if an access token needs to be auto-refreshed. If the amount of time since 364 * the last refresh exceeds the access token's expiration time, then the access token will be refreshed.</p> 365 * 366 * @param lastRefresh the new last refresh time in milliseconds. 367 */ 368 public void setLastRefresh(long lastRefresh) { 369 this.lastRefresh = lastRefresh; 370 } 371 372 /** 373 * Enables or disables automatic refreshing of this connection's access token. Defaults to true. 374 * @param autoRefresh true to enable auto token refresh; otherwise false. 375 */ 376 public void setAutoRefresh(boolean autoRefresh) { 377 this.autoRefresh = autoRefresh; 378 } 379 380 /** 381 * Gets whether or not automatic refreshing of this connection's access token is enabled. Defaults to true. 382 * @return true if auto token refresh is enabled; otherwise false. 383 */ 384 public boolean getAutoRefresh() { 385 return this.autoRefresh; 386 } 387 388 /** 389 * Gets the maximum number of times an API request will be tried when an error occurs. 390 * @return the maximum number of request attempts. 391 */ 392 public int getMaxRequestAttempts() { 393 return this.maxRequestAttempts; 394 } 395 396 /** 397 * Sets the maximum number of times an API request will be tried when an error occurs. 398 * @param attempts the maximum number of request attempts. 399 */ 400 public void setMaxRequestAttempts(int attempts) { 401 this.maxRequestAttempts = attempts; 402 } 403 404 /** 405 * Gets the proxy value to use for API calls to Box. 406 * @return the current proxy. 407 */ 408 public Proxy getProxy() { 409 return this.proxy; 410 } 411 412 /** 413 * Sets the proxy to use for API calls to Box. 414 * @param proxy the proxy to use for API calls to Box. 415 */ 416 public void setProxy(Proxy proxy) { 417 this.proxy = proxy; 418 } 419 420 /** 421 * Gets the username to use for a proxy that requires basic auth. 422 * @return the username to use for a proxy that requires basic auth. 423 */ 424 public String getProxyUsername() { 425 return this.proxyUsername; 426 } 427 428 /** 429 * Sets the username to use for a proxy that requires basic auth. 430 * @param proxyUsername the username to use for a proxy that requires basic auth. 431 */ 432 public void setProxyUsername(String proxyUsername) { 433 this.proxyUsername = proxyUsername; 434 } 435 436 /** 437 * Gets the password to use for a proxy that requires basic auth. 438 * @return the password to use for a proxy that requires basic auth. 439 */ 440 public String getProxyPassword() { 441 return this.proxyPassword; 442 } 443 444 /** 445 * Sets the password to use for a proxy that requires basic auth. 446 * @param proxyPassword the password to use for a proxy that requires basic auth. 447 */ 448 public void setProxyPassword(String proxyPassword) { 449 this.proxyPassword = proxyPassword; 450 } 451 452 /** 453 * Determines if this connection's access token can be refreshed. An access token cannot be refreshed if a refresh 454 * token was never set. 455 * @return true if the access token can be refreshed; otherwise false. 456 */ 457 public boolean canRefresh() { 458 return this.refreshToken != null; 459 } 460 461 /** 462 * Determines if this connection's access token has expired and needs to be refreshed. 463 * @return true if the access token needs to be refreshed; otherwise false. 464 */ 465 public boolean needsRefresh() { 466 boolean needsRefresh; 467 468 this.refreshLock.readLock().lock(); 469 long now = System.currentTimeMillis(); 470 long tokenDuration = (now - this.lastRefresh); 471 needsRefresh = (tokenDuration >= this.expires - REFRESH_EPSILON); 472 this.refreshLock.readLock().unlock(); 473 474 return needsRefresh; 475 } 476 477 /** 478 * Refresh's this connection's access token using its refresh token. 479 * @throws IllegalStateException if this connection's access token cannot be refreshed. 480 */ 481 public void refresh() { 482 this.refreshLock.writeLock().lock(); 483 484 if (!this.canRefresh()) { 485 this.refreshLock.writeLock().unlock(); 486 throw new IllegalStateException("The BoxAPIConnection cannot be refreshed because it doesn't have a " 487 + "refresh token."); 488 } 489 490 URL url = null; 491 try { 492 url = new URL(this.tokenURL); 493 } catch (MalformedURLException e) { 494 this.refreshLock.writeLock().unlock(); 495 assert false : "An invalid refresh URL indicates a bug in the SDK."; 496 throw new RuntimeException("An invalid refresh URL indicates a bug in the SDK.", e); 497 } 498 499 String urlParameters = String.format("grant_type=refresh_token&refresh_token=%s&client_id=%s&client_secret=%s", 500 this.refreshToken, this.clientID, this.clientSecret); 501 502 BoxAPIRequest request = new BoxAPIRequest(this, url, "POST"); 503 request.shouldAuthenticate(false); 504 request.setBody(urlParameters); 505 506 String json; 507 try { 508 BoxJSONResponse response = (BoxJSONResponse) request.send(); 509 json = response.getJSON(); 510 } catch (BoxAPIException e) { 511 this.notifyError(e); 512 this.refreshLock.writeLock().unlock(); 513 throw e; 514 } 515 516 JsonObject jsonObject = JsonObject.readFrom(json); 517 this.accessToken = jsonObject.get("access_token").asString(); 518 this.refreshToken = jsonObject.get("refresh_token").asString(); 519 this.lastRefresh = System.currentTimeMillis(); 520 this.expires = jsonObject.get("expires_in").asLong() * 1000; 521 522 this.notifyRefresh(); 523 524 this.refreshLock.writeLock().unlock(); 525 } 526 527 /** 528 * Restores a saved connection state into this BoxAPIConnection. 529 * 530 * @see #save 531 * @param state the saved state that was created with {@link #save}. 532 */ 533 public void restore(String state) { 534 JsonObject json = JsonObject.readFrom(state); 535 String accessToken = json.get("accessToken").asString(); 536 String refreshToken = json.get("refreshToken").asString(); 537 long lastRefresh = json.get("lastRefresh").asLong(); 538 long expires = json.get("expires").asLong(); 539 String userAgent = json.get("userAgent").asString(); 540 String tokenURL = json.get("tokenURL").asString(); 541 String baseURL = json.get("baseURL").asString(); 542 String baseUploadURL = json.get("baseUploadURL").asString(); 543 boolean autoRefresh = json.get("autoRefresh").asBoolean(); 544 int maxRequestAttempts = json.get("maxRequestAttempts").asInt(); 545 546 this.accessToken = accessToken; 547 this.refreshToken = refreshToken; 548 this.lastRefresh = lastRefresh; 549 this.expires = expires; 550 this.userAgent = userAgent; 551 this.tokenURL = tokenURL; 552 this.baseURL = baseURL; 553 this.baseUploadURL = baseUploadURL; 554 this.autoRefresh = autoRefresh; 555 this.maxRequestAttempts = maxRequestAttempts; 556 } 557 558 /** 559 * Notifies a refresh event to all the listeners. 560 */ 561 protected void notifyRefresh() { 562 for (BoxAPIConnectionListener listener : this.listeners) { 563 listener.onRefresh(this); 564 } 565 } 566 567 /** 568 * Notifies an error event to all the listeners. 569 * @param error A BoxAPIException instance. 570 */ 571 protected void notifyError(BoxAPIException error) { 572 for (BoxAPIConnectionListener listener : this.listeners) { 573 listener.onError(this, error); 574 } 575 } 576 577 /** 578 * Add a listener to listen to Box API connection events. 579 * @param listener a listener to listen to Box API connection. 580 */ 581 public void addListener(BoxAPIConnectionListener listener) { 582 this.listeners.add(listener); 583 } 584 585 /** 586 * Remove a listener listening to Box API connection events. 587 * @param listener the listener to remove. 588 */ 589 public void removeListener(BoxAPIConnectionListener listener) { 590 this.listeners.remove(listener); 591 } 592 593 /** 594 * Gets the RequestInterceptor associated with this API connection. 595 * @return the RequestInterceptor associated with this API connection. 596 */ 597 public RequestInterceptor getRequestInterceptor() { 598 return this.interceptor; 599 } 600 601 /** 602 * Sets a RequestInterceptor that can intercept requests and manipulate them before they're sent to the Box API. 603 * @param interceptor the RequestInterceptor. 604 */ 605 public void setRequestInterceptor(RequestInterceptor interceptor) { 606 this.interceptor = interceptor; 607 } 608 609 /** 610 * Get a lower-scoped token restricted to a resource for the list of scopes that are passed. 611 * @param scopes the list of scopes to which the new token should be restricted for 612 * @param resource the resource for which the new token has to be obtained 613 * @return scopedToken which has access token and other details 614 */ 615 public ScopedToken getLowerScopedToken(List<String> scopes, String resource) { 616 assert (scopes != null); 617 assert (scopes.size() > 0); 618 URL url = null; 619 try { 620 url = new URL(this.getTokenURL()); 621 } catch (MalformedURLException e) { 622 assert false : "An invalid refresh URL indicates a bug in the SDK."; 623 throw new RuntimeException("An invalid refresh URL indicates a bug in the SDK.", e); 624 } 625 626 StringBuilder spaceSeparatedScopes = new StringBuilder(); 627 for (int i = 0; i < scopes.size(); i++) { 628 spaceSeparatedScopes.append(scopes.get(i)); 629 if (i < scopes.size() - 1) { 630 spaceSeparatedScopes.append(" "); 631 } 632 } 633 634 String urlParameters = null; 635 636 if (resource != null) { 637 //this.getAccessToken() ensures we have a valid access token 638 urlParameters = String.format("grant_type=urn:ietf:params:oauth:grant-type:token-exchange" 639 + "&subject_token_type=urn:ietf:params:oauth:token-type:access_token&subject_token=%s" 640 + "&scope=%s&resource=%s", 641 this.getAccessToken(), spaceSeparatedScopes, resource); 642 } else { 643 //this.getAccessToken() ensures we have a valid access token 644 urlParameters = String.format("grant_type=urn:ietf:params:oauth:grant-type:token-exchange" 645 + "&subject_token_type=urn:ietf:params:oauth:token-type:access_token&subject_token=%s" 646 + "&scope=%s", 647 this.getAccessToken(), spaceSeparatedScopes); 648 } 649 650 BoxAPIRequest request = new BoxAPIRequest(this, url, "POST"); 651 request.shouldAuthenticate(false); 652 request.setBody(urlParameters); 653 654 String json; 655 try { 656 BoxJSONResponse response = (BoxJSONResponse) request.send(); 657 json = response.getJSON(); 658 } catch (BoxAPIException e) { 659 this.notifyError(e); 660 throw e; 661 } 662 663 JsonObject jsonObject = JsonObject.readFrom(json); 664 ScopedToken token = new ScopedToken(jsonObject); 665 token.setObtainedAt(System.currentTimeMillis()); 666 token.setExpiresIn(jsonObject.get("expires_in").asLong() * 1000); 667 return token; 668 } 669 670 /** 671 * Saves the state of this connection to a string so that it can be persisted and restored at a later time. 672 * 673 * <p>Note that proxy settings aren't automatically saved or restored. This is mainly due to security concerns 674 * around persisting proxy authentication details to the state string. If your connection uses a proxy, you will 675 * have to manually configure it again after restoring the connection.</p> 676 * 677 * @see #restore 678 * @return the state of this connection. 679 */ 680 public String save() { 681 JsonObject state = new JsonObject() 682 .add("accessToken", this.accessToken) 683 .add("refreshToken", this.refreshToken) 684 .add("lastRefresh", this.lastRefresh) 685 .add("expires", this.expires) 686 .add("userAgent", this.userAgent) 687 .add("tokenURL", this.tokenURL) 688 .add("baseURL", this.baseURL) 689 .add("baseUploadURL", this.baseUploadURL) 690 .add("autoRefresh", this.autoRefresh) 691 .add("maxRequestAttempts", this.maxRequestAttempts); 692 return state.toString(); 693 } 694 695 String lockAccessToken() { 696 if (this.autoRefresh && this.canRefresh() && this.needsRefresh()) { 697 this.refreshLock.writeLock().lock(); 698 try { 699 if (this.needsRefresh()) { 700 this.refresh(); 701 } 702 this.refreshLock.readLock().lock(); 703 } finally { 704 this.refreshLock.writeLock().unlock(); 705 } 706 } else { 707 this.refreshLock.readLock().lock(); 708 } 709 710 return this.accessToken; 711 } 712 713 void unlockAccessToken() { 714 this.refreshLock.readLock().unlock(); 715 } 716}