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