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