001package com.box.sdk; 002 003import static com.box.sdk.BinaryBodyUtils.writeStream; 004import static com.box.sdk.BinaryBodyUtils.writeStreamWithContentLength; 005import static com.box.sdk.http.ContentType.APPLICATION_JSON; 006import static com.box.sdk.http.ContentType.APPLICATION_JSON_PATCH; 007import static com.eclipsesource.json.Json.NULL; 008 009import com.box.sdk.http.HttpMethod; 010import com.box.sdk.internal.utils.Parsers; 011import com.box.sdk.sharedlink.BoxSharedLinkRequest; 012import com.eclipsesource.json.Json; 013import com.eclipsesource.json.JsonArray; 014import com.eclipsesource.json.JsonObject; 015import com.eclipsesource.json.JsonValue; 016import java.io.IOException; 017import java.io.InputStream; 018import java.io.OutputStream; 019import java.net.MalformedURLException; 020import java.net.URL; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collection; 024import java.util.Date; 025import java.util.EnumSet; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Optional; 030import java.util.Set; 031import java.util.concurrent.TimeUnit; 032 033/** 034 * Represents an individual file on Box. This class can be used to download a file's contents, 035 * upload new versions, and perform other common file operations (move, copy, delete, etc.). 036 * 037 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link 038 * BoxAPIException} (unchecked meaning that the compiler won't force you to handle it) if an error 039 * occurs. If you wish to implement custom error handling for errors related to the Box REST API, 040 * you should capture this exception explicitly. 041 */ 042@BoxResourceType("file") 043public class BoxFile extends BoxItem { 044 045 /** 046 * An array of all possible file fields that can be requested when calling {@link 047 * #getInfo(String...)}. 048 */ 049 public static final String[] ALL_FIELDS = { 050 "type", 051 "id", 052 "sequence_id", 053 "etag", 054 "sha1", 055 "name", 056 "description", 057 "size", 058 "path_collection", 059 "created_at", 060 "modified_at", 061 "trashed_at", 062 "purged_at", 063 "content_created_at", 064 "content_modified_at", 065 "created_by", 066 "modified_by", 067 "owned_by", 068 "shared_link", 069 "parent", 070 "item_status", 071 "version_number", 072 "comment_count", 073 "permissions", 074 "tags", 075 "lock", 076 "extension", 077 "is_package", 078 "file_version", 079 "collections", 080 "watermark_info", 081 "metadata", 082 "representations", 083 "is_external_only", 084 "expiring_embed_link", 085 "allowed_invitee_roles", 086 "has_collaborations", 087 "disposition_at", 088 "is_accessible_via_shared_link" 089 }; 090 091 /** 092 * An array of all possible version fields that can be requested when calling {@link 093 * #getVersions(String...)}. 094 */ 095 public static final String[] ALL_VERSION_FIELDS = { 096 "id", 097 "sha1", 098 "name", 099 "size", 100 "uploader_display_name", 101 "created_at", 102 "modified_at", 103 "modified_by", 104 "trashed_at", 105 "trashed_by", 106 "restored_at", 107 "restored_by", 108 "purged_at", 109 "file_version", 110 "version_number" 111 }; 112 /** File URL Template. */ 113 public static final URLTemplate FILE_URL_TEMPLATE = new URLTemplate("files/%s"); 114 /** Content URL Template. */ 115 public static final URLTemplate CONTENT_URL_TEMPLATE = new URLTemplate("files/%s/content"); 116 /** Versions URL Template. */ 117 public static final URLTemplate VERSIONS_URL_TEMPLATE = new URLTemplate("files/%s/versions"); 118 /** Copy URL Template. */ 119 public static final URLTemplate COPY_URL_TEMPLATE = new URLTemplate("files/%s/copy"); 120 /** Add Comment URL Template. */ 121 public static final URLTemplate ADD_COMMENT_URL_TEMPLATE = new URLTemplate("comments"); 122 /** Get Comments URL Template. */ 123 public static final URLTemplate GET_COMMENTS_URL_TEMPLATE = new URLTemplate("files/%s/comments"); 124 /** Metadata URL Template. */ 125 public static final URLTemplate METADATA_URL_TEMPLATE = 126 new URLTemplate("files/%s/metadata/%s/%s"); 127 /** Add Task URL Template. */ 128 public static final URLTemplate ADD_TASK_URL_TEMPLATE = new URLTemplate("tasks"); 129 /** Get Tasks URL Template. */ 130 public static final URLTemplate GET_TASKS_URL_TEMPLATE = new URLTemplate("files/%s/tasks"); 131 /** Get Thumbnail PNG Template. */ 132 public static final URLTemplate GET_THUMBNAIL_PNG_TEMPLATE = 133 new URLTemplate("files/%s/thumbnail.png"); 134 /** Get Thumbnail JPG Template. */ 135 public static final URLTemplate GET_THUMBNAIL_JPG_TEMPLATE = 136 new URLTemplate("files/%s/thumbnail.jpg"); 137 /** Upload Session URL Template. */ 138 public static final URLTemplate UPLOAD_SESSION_URL_TEMPLATE = 139 new URLTemplate("files/%s/upload_sessions"); 140 /** Upload Session Status URL Template. */ 141 public static final URLTemplate UPLOAD_SESSION_STATUS_URL_TEMPLATE = 142 new URLTemplate("files/upload_sessions/%s/status"); 143 /** Abort Upload Session URL Template. */ 144 public static final URLTemplate ABORT_UPLOAD_SESSION_URL_TEMPLATE = 145 new URLTemplate("files/upload_sessions/%s"); 146 /** Add Collaborations URL Template. */ 147 public static final URLTemplate ADD_COLLABORATION_URL = new URLTemplate("collaborations"); 148 /** Get All File Collaborations URL Template. */ 149 public static final URLTemplate GET_ALL_FILE_COLLABORATIONS_URL = 150 new URLTemplate("files/%s/collaborations"); 151 /** Describes file item type. */ 152 static final String TYPE = "file"; 153 154 private static final int GET_COLLABORATORS_PAGE_SIZE = 1000; 155 156 /** 157 * Constructs a BoxFile for a file with a given ID. 158 * 159 * @param api the API connection to be used by the file. 160 * @param id the ID of the file. 161 */ 162 public BoxFile(BoxAPIConnection api, String id) { 163 super(api, id); 164 } 165 166 /** {@inheritDoc} */ 167 @Override 168 protected URL getItemURL() { 169 return FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 170 } 171 172 /** 173 * Creates a shared link. 174 * 175 * @param sharedLinkRequest Shared link to create 176 * @return Created shared link. 177 */ 178 public BoxSharedLink createSharedLink(BoxSharedLinkRequest sharedLinkRequest) { 179 return createSharedLink(sharedLinkRequest.asSharedLink()); 180 } 181 182 private BoxSharedLink createSharedLink(BoxSharedLink sharedLink) { 183 Info info = new Info(); 184 info.setSharedLink(sharedLink); 185 186 this.updateInfo(info); 187 return info.getSharedLink(); 188 } 189 190 /** 191 * Adds new {@link BoxWebHook} to this {@link BoxFile}. 192 * 193 * @param address {@link BoxWebHook.Info#getAddress()} 194 * @param triggers {@link BoxWebHook.Info#getTriggers()} 195 * @return created {@link BoxWebHook.Info} 196 */ 197 public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) { 198 return BoxWebHook.create(this, address, triggers); 199 } 200 201 /** 202 * Adds a comment to this file. The message can contain @mentions by using the 203 * string @[userid:username] anywhere within the message, where userid and username are the ID and 204 * username of the person being mentioned. 205 * 206 * @param message the comment's message. 207 * @return information about the newly added comment. 208 * @see <a href="https://developers.box.com/docs/#comments-add-a-comment-to-an-item">the 209 * tagged_message field for including @mentions.</a> 210 */ 211 public BoxComment.Info addComment(String message) { 212 JsonObject itemJSON = new JsonObject(); 213 itemJSON.add("type", "file"); 214 itemJSON.add("id", this.getID()); 215 216 JsonObject requestJSON = new JsonObject(); 217 requestJSON.add("item", itemJSON); 218 if (BoxComment.messageContainsMention(message)) { 219 requestJSON.add("tagged_message", message); 220 } else { 221 requestJSON.add("message", message); 222 } 223 224 URL url = ADD_COMMENT_URL_TEMPLATE.build(this.getAPI().getBaseURL()); 225 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); 226 request.setBody(requestJSON.toString()); 227 try (BoxJSONResponse response = request.send()) { 228 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 229 230 BoxComment addedComment = new BoxComment(this.getAPI(), responseJSON.get("id").asString()); 231 return addedComment.new Info(responseJSON); 232 } 233 } 234 235 /** 236 * Adds a new task to this file. The task can have an optional message to include, and a due date. 237 * 238 * @param action the action the task assignee will be prompted to do. 239 * @param message an optional message to include with the task. 240 * @param dueAt the day at which this task is due. 241 * @return information about the newly added task. 242 */ 243 public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt) { 244 return this.addTask(action, message, dueAt, null); 245 } 246 247 /** 248 * Adds a new task to this file. The task can have an optional message to include, due date, and 249 * task completion rule. 250 * 251 * @param action the action the task assignee will be prompted to do. 252 * @param message an optional message to include with the task. 253 * @param dueAt the day at which this task is due. 254 * @param completionRule the rule for completing the task. 255 * @return information about the newly added task. 256 */ 257 public BoxTask.Info addTask( 258 BoxTask.Action action, String message, Date dueAt, BoxTask.CompletionRule completionRule) { 259 JsonObject itemJSON = new JsonObject(); 260 itemJSON.add("type", "file"); 261 itemJSON.add("id", this.getID()); 262 263 JsonObject requestJSON = new JsonObject(); 264 requestJSON.add("item", itemJSON); 265 requestJSON.add("action", action.toJSONString()); 266 267 if (message != null && !message.isEmpty()) { 268 requestJSON.add("message", message); 269 } 270 271 if (dueAt != null) { 272 requestJSON.add("due_at", BoxDateFormat.format(dueAt)); 273 } 274 275 if (completionRule != null) { 276 requestJSON.add("completion_rule", completionRule.toJSONString()); 277 } 278 279 URL url = ADD_TASK_URL_TEMPLATE.build(this.getAPI().getBaseURL()); 280 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); 281 request.setBody(requestJSON.toString()); 282 try (BoxJSONResponse response = request.send()) { 283 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 284 285 BoxTask addedTask = new BoxTask(this.getAPI(), responseJSON.get("id").asString()); 286 return addedTask.new Info(responseJSON); 287 } 288 } 289 290 /** 291 * Gets an expiring URL for downloading a file directly from Box. This can be user, for example, 292 * for sending as a redirect to a browser to cause the browser to download the file directly from 293 * Box. 294 * 295 * @return the temporary download URL 296 */ 297 public URL getDownloadURL() { 298 URL url = getDownloadUrl(); 299 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 300 request.setFollowRedirects(false); 301 302 try (BoxAPIResponse response = request.send()) { 303 String location = response.getHeaderField("location"); 304 305 try { 306 return new URL(location); 307 } catch (MalformedURLException e) { 308 throw new RuntimeException(e); 309 } 310 } 311 } 312 313 /** 314 * Downloads the contents of this file to a given OutputStream. 315 * 316 * @param output the stream to where the file will be written. 317 */ 318 public void download(OutputStream output) { 319 this.download(output, null); 320 } 321 322 /** 323 * Downloads the contents of this file to a given OutputStream while reporting the progress to a 324 * ProgressListener. 325 * 326 * @param output the stream to where the file will be written. 327 * @param listener a listener for monitoring the download's progress. 328 */ 329 public void download(OutputStream output, ProgressListener listener) { 330 URL url = getDownloadUrl(); 331 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 332 BoxAPIResponse response = request.send(); 333 writeStream(response, output, listener); 334 } 335 336 /** 337 * Downloads the content of the file to a given OutputStream using the provided shared link. 338 * 339 * @param api the API connection to be used to get download URL of the file. 340 * @param output the stream to where the file will be written. 341 * @param sharedLink the shared link of the file. 342 */ 343 public static void downloadFromSharedLink( 344 BoxAPIConnection api, OutputStream output, String sharedLink) { 345 downloadFromSharedLink(api, output, sharedLink, null, null); 346 } 347 348 /** 349 * Downloads the content of the file to a given OutputStream using the provided shared link. 350 * 351 * @param api the API connection to be used to get download URL of the file. 352 * @param output the stream to where the file will be written. 353 * @param sharedLink the shared link of the file. 354 * @param password the password for the shared link. 355 */ 356 public static void downloadFromSharedLink( 357 BoxAPIConnection api, OutputStream output, String sharedLink, String password) { 358 downloadFromSharedLink(api, output, sharedLink, password, null); 359 } 360 361 /** 362 * Downloads the content of the file to a given OutputStream using the provided shared link. 363 * 364 * @param api the API connection to be used to get download URL of the file. 365 * @param output the stream to where the file will be written. 366 * @param sharedLink the shared link of the file. 367 * @param listener a listener for monitoring the download's progress. 368 */ 369 public static void downloadFromSharedLink( 370 BoxAPIConnection api, OutputStream output, String sharedLink, ProgressListener listener) { 371 downloadFromSharedLink(api, output, sharedLink, null, listener); 372 } 373 374 /** 375 * Downloads the content of the file to a given OutputStream using the provided shared link. 376 * 377 * @param api the API connection to be used to get download URL of the file. 378 * @param output the stream to where the file will be written. 379 * @param sharedLink the shared link of the file. 380 * @param password the password for the shared link. 381 * @param listener a listener for monitoring the download's progress. 382 */ 383 public static void downloadFromSharedLink( 384 BoxAPIConnection api, 385 OutputStream output, 386 String sharedLink, 387 String password, 388 ProgressListener listener) { 389 BoxItem.Info item = BoxItem.getSharedItem(api, sharedLink, password, "id"); 390 if (!(item instanceof BoxFile.Info)) { 391 throw new BoxAPIException("The shared link provided is not a shared link for a file."); 392 } 393 BoxFile sharedFile = new BoxFile(api, item.getID()); 394 URL url = sharedFile.getDownloadUrl(); 395 BoxAPIRequest request = new BoxAPIRequest(api, url, "GET"); 396 request.addHeader("BoxApi", BoxSharedLink.getSharedLinkHeaderValue(sharedLink, password)); 397 BoxAPIResponse response = request.send(); 398 writeStream(response, output, listener); 399 } 400 401 /** 402 * Downloads a part of this file's contents, starting at specified byte offset. 403 * 404 * @param output the stream to where the file will be written. 405 * @param offset the byte offset at which to start the download. 406 */ 407 public void downloadRange(OutputStream output, long offset) { 408 this.downloadRange(output, offset, -1); 409 } 410 411 /** 412 * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd. 413 * 414 * @param output the stream to where the file will be written. 415 * @param rangeStart the byte offset at which to start the download. 416 * @param rangeEnd the byte offset at which to stop the download. 417 */ 418 public void downloadRange(OutputStream output, long rangeStart, long rangeEnd) { 419 this.downloadRange(output, rangeStart, rangeEnd, null); 420 } 421 422 /** 423 * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd, 424 * while reporting the progress to a ProgressListener. 425 * 426 * @param output the stream to where the file will be written. 427 * @param rangeStart the byte offset at which to start the download. 428 * @param rangeEnd the byte offset at which to stop the download. 429 * @param listener a listener for monitoring the download's progress. 430 */ 431 public void downloadRange( 432 OutputStream output, long rangeStart, long rangeEnd, ProgressListener listener) { 433 URL url = getDownloadUrl(); 434 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 435 if (rangeEnd > 0) { 436 request.addHeader("Range", String.format("bytes=%s-%s", rangeStart, rangeEnd)); 437 } else { 438 request.addHeader("Range", String.format("bytes=%s-", rangeStart)); 439 } 440 writeStream(request.send(), output, listener); 441 } 442 443 /** 444 * Can be used to override the URL used for file download. 445 * 446 * @return URL for file downalod 447 */ 448 protected URL getDownloadUrl() { 449 return CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 450 } 451 452 @Override 453 public BoxFile.Info copy(BoxFolder destination) { 454 return this.copy(destination, null); 455 } 456 457 @Override 458 public BoxFile.Info copy(BoxFolder destination, String newName) { 459 URL url = COPY_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 460 461 JsonObject parent = new JsonObject(); 462 parent.add("id", destination.getID()); 463 464 JsonObject copyInfo = new JsonObject(); 465 copyInfo.add("parent", parent); 466 if (newName != null) { 467 copyInfo.add("name", newName); 468 } 469 470 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); 471 request.setBody(copyInfo.toString()); 472 try (BoxJSONResponse response = request.send()) { 473 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 474 BoxFile copiedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString()); 475 return copiedFile.new Info(responseJSON); 476 } 477 } 478 479 /** Deletes this file by moving it to the trash. */ 480 public void delete() { 481 URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 482 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE"); 483 request.send().close(); 484 } 485 486 @Override 487 public BoxItem.Info move(BoxFolder destination) { 488 return this.move(destination, null); 489 } 490 491 @Override 492 public BoxItem.Info move(BoxFolder destination, String newName) { 493 URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 494 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); 495 496 JsonObject parent = new JsonObject(); 497 parent.add("id", destination.getID()); 498 499 JsonObject updateInfo = new JsonObject(); 500 updateInfo.add("parent", parent); 501 if (newName != null) { 502 updateInfo.add("name", newName); 503 } 504 505 request.setBody(updateInfo.toString()); 506 try (BoxJSONResponse response = request.send()) { 507 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 508 BoxFile movedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString()); 509 return movedFile.new Info(responseJSON); 510 } 511 } 512 513 /** 514 * Renames this file. 515 * 516 * @param newName the new name of the file. 517 */ 518 public void rename(String newName) { 519 URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 520 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); 521 522 JsonObject updateInfo = new JsonObject(); 523 updateInfo.add("name", newName); 524 525 request.setBody(updateInfo.toString()); 526 try (BoxJSONResponse response = request.send()) { 527 response.getJSON(); 528 } 529 } 530 531 @Override 532 public BoxFile.Info getInfo(String... fields) { 533 URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 534 if (fields.length > 0) { 535 String queryString = new QueryStringBuilder().appendParam("fields", fields).toString(); 536 url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 537 } 538 539 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); 540 try (BoxJSONResponse response = request.send()) { 541 return new Info(response.getJSON()); 542 } 543 } 544 545 /** 546 * Gets information about this item including a specified set of representations. 547 * 548 * @param representationHints hints for representations to be retrieved 549 * @param fields the fields to retrieve. 550 * @return info about this item containing only the specified fields, including representations. 551 * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints 552 * Header</a> 553 */ 554 public BoxFile.Info getInfoWithRepresentations(String representationHints, String... fields) { 555 if (representationHints.matches(Representation.X_REP_HINTS_PATTERN)) { 556 // Since the user intends to get representations, add it to fields, even if user has missed it 557 Set<String> fieldsSet = new HashSet<>(Arrays.asList(fields)); 558 fieldsSet.add("representations"); 559 String queryString = 560 new QueryStringBuilder() 561 .appendParam("fields", fieldsSet.toArray(new String[0])) 562 .toString(); 563 URL url = 564 FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 565 566 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); 567 request.addHeader("X-Rep-Hints", representationHints); 568 try (BoxJSONResponse response = request.send()) { 569 return new Info(response.getJSON()); 570 } 571 } else { 572 throw new BoxAPIException( 573 "Represention hints is not valid. Refer documention on how to construct X-Rep-Hints Header"); 574 } 575 } 576 577 /** 578 * Fetches the contents of a file representation and writes them to the provided output stream. 579 * 580 * @param representationHint the X-Rep-Hints query for the representation to fetch. 581 * @param output the output stream to write the contents to. 582 * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints 583 * Header</a> 584 */ 585 public void getRepresentationContent(String representationHint, OutputStream output) { 586 587 this.getRepresentationContent(representationHint, "", output); 588 } 589 590 /** 591 * Fetches the contents of a file representation with asset path and writes them to the provided 592 * output stream. 593 * 594 * @param representationHint the X-Rep-Hints query for the representation to fetch. 595 * @param assetPath the path of the asset for representations containing multiple files. 596 * @param output the output stream to write the contents to. 597 * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints 598 * Header</a> 599 */ 600 public void getRepresentationContent( 601 String representationHint, String assetPath, OutputStream output) { 602 this.getRepresentationContent(representationHint, assetPath, output, Integer.MAX_VALUE); 603 } 604 605 /** 606 * Fetches the contents of a file representation with asset path and writes them to the provided 607 * output stream. 608 * 609 * @param representationHint the X-Rep-Hints query for the representation to fetch. 610 * @param assetPath the path of the asset for representations containing multiple files. 611 * @param output the output stream to write the contents to. 612 * @param maxRetries the maximum number of attempts to call the request for retrieving status 613 * information indicating whether the representation has been generated and is ready to fetch. 614 * If the number of attempts is exceeded, the method will throw a BoxApiException. 615 * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints 616 * Header</a> 617 */ 618 public void getRepresentationContent( 619 String representationHint, String assetPath, OutputStream output, int maxRetries) { 620 List<Representation> reps = 621 this.getInfoWithRepresentations(representationHint).getRepresentations(); 622 if (reps.size() < 1) { 623 throw new BoxAPIException( 624 "No matching representations found for requested '" + representationHint + "' hint"); 625 } 626 Representation representation = reps.get(0); 627 String repState = representation.getStatus().getState(); 628 629 switch (repState) { 630 case "viewable": 631 case "success": 632 this.makeRepresentationContentRequest( 633 representation.getContent().getUrlTemplate(), assetPath, output); 634 break; 635 case "pending": 636 case "none": 637 String repContentURLString = null; 638 int attemptNumber = 0; 639 while (repContentURLString == null && attemptNumber < maxRetries) { 640 repContentURLString = this.pollRepInfo(representation.getInfo().getUrl()); 641 try { 642 Thread.sleep(100); 643 } catch (InterruptedException e) { 644 throw new RuntimeException(e); 645 } 646 attemptNumber++; 647 } 648 649 if (repContentURLString != null) { 650 this.makeRepresentationContentRequest(repContentURLString, assetPath, output); 651 } else { 652 throw new BoxAPIException( 653 "Representation did not have a success status allowing it to be retrieved after " 654 + maxRetries 655 + " attempts"); 656 } 657 658 break; 659 case "error": 660 throw new BoxAPIException("Representation had error status"); 661 default: 662 throw new BoxAPIException("Representation had unknown status"); 663 } 664 } 665 666 private String pollRepInfo(URL infoURL) { 667 668 BoxJSONRequest infoRequest = new BoxJSONRequest(this.getAPI(), infoURL, HttpMethod.GET); 669 try (BoxJSONResponse infoResponse = infoRequest.send()) { 670 JsonObject response = infoResponse.getJsonObject(); 671 672 Representation rep = new Representation(response); 673 674 String repState = rep.getStatus().getState(); 675 676 switch (repState) { 677 case "viewable": 678 case "success": 679 return rep.getContent().getUrlTemplate(); 680 case "pending": 681 case "none": 682 return null; 683 case "error": 684 throw new BoxAPIException("Representation had error status"); 685 default: 686 throw new BoxAPIException("Representation had unknown status"); 687 } 688 } 689 } 690 691 private void makeRepresentationContentRequest( 692 String representationURLTemplate, String assetPath, OutputStream output) { 693 try { 694 URL repURL = new URL(representationURLTemplate.replace("{+asset_path}", assetPath)); 695 BoxAPIRequest repContentReq = new BoxAPIRequest(this.getAPI(), repURL, HttpMethod.GET); 696 BoxAPIResponse response = repContentReq.send(); 697 writeStreamWithContentLength(response, output); 698 } catch (MalformedURLException ex) { 699 700 throw new BoxAPIException("Could not generate representation content URL"); 701 } 702 } 703 704 /** 705 * Updates the information about this file with any info fields that have been modified locally. 706 * 707 * <p>The only fields that will be updated are the ones that have been modified locally. For 708 * example, the following code won't update any information (or even send a network request) since 709 * none of the info's fields were changed: 710 * 711 * <pre>BoxFile file = new File(api, id); 712 * BoxFile.Info info = file.getInfo(); 713 * file.updateInfo(info);</pre> 714 * 715 * @param info the updated info. 716 */ 717 public void updateInfo(BoxFile.Info info) { 718 URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 719 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); 720 request.setBody(info.getPendingChanges()); 721 try (BoxJSONResponse response = request.send()) { 722 JsonObject jsonObject = Json.parse(response.getJSON()).asObject(); 723 info.update(jsonObject); 724 } 725 } 726 727 /** 728 * Retrieve a specific file version. 729 * 730 * @param fileVersionID the ID of the file version to retrieve. 731 * @param fields the optional fields to retrieve. 732 * @return a specific file version. 733 */ 734 public BoxFileVersion getVersionByID(String fileVersionID, String... fields) { 735 URL url = 736 BoxFileVersion.VERSION_URL_TEMPLATE.build( 737 this.getAPI().getBaseURL(), this.getID(), fileVersionID); 738 if (fields.length > 0) { 739 String queryString = new QueryStringBuilder().appendParam("fields", fields).toString(); 740 url = 741 BoxFileVersion.VERSION_URL_TEMPLATE.buildWithQuery( 742 this.getAPI().getBaseURL(), queryString, this.getID(), fileVersionID); 743 } 744 745 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); 746 try (BoxJSONResponse response = request.send()) { 747 JsonObject jsonObject = Json.parse(response.getJSON()).asObject(); 748 return new BoxFileVersion(this.getAPI(), jsonObject, this.getID()); 749 } 750 } 751 752 /** 753 * Gets up to 1000 versions of this file. Note that only users with premium accounts will be able 754 * to retrieve previous versions of their files. `fields` parameter is optional, if specified only 755 * requested fields will be returned: 756 * 757 * <pre>{@code 758 * new BoxFile(api, file_id).getVersions() // will return all default fields 759 * new BoxFile(api, file_id).getVersions("name") // will return only specified fields 760 * }</pre> 761 * 762 * @param fields the fields to retrieve. If nothing provided default fields will be returned. You 763 * can find list of available fields at {@link BoxFile#ALL_VERSION_FIELDS} 764 * @return a list of previous file versions. 765 */ 766 public Collection<BoxFileVersion> getVersions(String... fields) { 767 return getVersionsRange(0, BoxFileVersion.DEFAULT_LIMIT, fields); 768 } 769 770 /** 771 * Retrieves a specific range of versions of this file. 772 * 773 * @param offset the index of the first version of this file to retrieve. 774 * @param limit the maximum number of versions to retrieve after the offset. 775 * @param fields the fields to retrieve. 776 * @return a partial collection containing the specified range of versions of this file. 777 */ 778 public PartialCollection<BoxFileVersion> getVersionsRange( 779 long offset, long limit, String... fields) { 780 QueryStringBuilder builder = 781 new QueryStringBuilder().appendParam("limit", limit).appendParam("offset", offset); 782 783 if (fields.length > 0) { 784 builder.appendParam("fields", fields); 785 } 786 787 URL url = 788 VERSIONS_URL_TEMPLATE.buildWithQuery( 789 this.getAPI().getBaseURL(), builder.toString(), this.getID()); 790 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); 791 try (BoxJSONResponse response = request.send()) { 792 793 JsonObject jsonObject = Json.parse(response.getJSON()).asObject(); 794 String totalCountString = jsonObject.get("total_count").toString(); 795 long fullSize = Double.valueOf(totalCountString).longValue(); 796 PartialCollection<BoxFileVersion> versions = new PartialCollection<>(offset, limit, fullSize); 797 JsonArray entries = jsonObject.get("entries").asArray(); 798 for (JsonValue entry : entries) { 799 versions.add(new BoxFileVersion(this.getAPI(), entry.asObject(), this.getID())); 800 } 801 802 return versions; 803 } 804 } 805 806 /** 807 * Checks if a new version of the file can be uploaded with the specified name. 808 * 809 * @param name the new name for the file. 810 * @return whether or not the file version can be uploaded. 811 */ 812 public boolean canUploadVersion(String name) { 813 return this.canUploadVersion(name, 0); 814 } 815 816 /** 817 * Checks if a new version of the file can be uploaded with the specified name and size. 818 * 819 * @param name the new name for the file. 820 * @param fileSize the size of the new version content in bytes. 821 * @return whether the file version can be uploaded. 822 */ 823 public boolean canUploadVersion(String name, long fileSize) { 824 825 URL url = getDownloadUrl(); 826 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS"); 827 828 JsonObject preflightInfo = new JsonObject(); 829 if (name != null) { 830 preflightInfo.add("name", name); 831 } 832 833 preflightInfo.add("size", fileSize); 834 835 request.setBody(preflightInfo.toString()); 836 try (BoxAPIResponse response = request.send()) { 837 return response.getResponseCode() == 200; 838 } catch (BoxAPIException ex) { 839 if (ex.getResponseCode() >= 400 && ex.getResponseCode() < 500) { 840 // This looks like an error response, meaning the upload would fail 841 return false; 842 } else { 843 // This looks like a network error or server error, rethrow exception 844 throw ex; 845 } 846 } 847 } 848 849 /** 850 * Uploads a new version of this file, replacing the current version. Note that only users with 851 * premium accounts will be able to view and recover previous versions of the file. 852 * 853 * @param fileContent a stream containing the new file contents. 854 * @return the uploaded file version. 855 */ 856 public BoxFile.Info uploadNewVersion(InputStream fileContent) { 857 return this.uploadNewVersion(fileContent, null); 858 } 859 860 /** 861 * Uploads a new version of this file, replacing the current version. Note that only users with 862 * premium accounts will be able to view and recover previous versions of the file. 863 * 864 * @param fileContent a stream containing the new file contents. 865 * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents. 866 * @return the uploaded file version. 867 */ 868 public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1) { 869 return this.uploadNewVersion(fileContent, fileContentSHA1, null); 870 } 871 872 /** 873 * Uploads a new version of this file, replacing the current version. Note that only users with 874 * premium accounts will be able to view and recover previous versions of the file. 875 * 876 * @param fileContent a stream containing the new file contents. 877 * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents. 878 * @param modified the date that the new version was modified. 879 * @return the uploaded file version. 880 */ 881 public BoxFile.Info uploadNewVersion( 882 InputStream fileContent, String fileContentSHA1, Date modified) { 883 return this.uploadNewVersion(fileContent, fileContentSHA1, modified, 0, null); 884 } 885 886 /** 887 * Uploads a new version of this file, replacing the current version. Note that only users with 888 * premium accounts will be able to view and recover previous versions of the file. 889 * 890 * @param fileContent a stream containing the new file contents. 891 * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents. 892 * @param modified the date that the new version was modified. 893 * @param name the new name for the file 894 * @return the uploaded file version. 895 */ 896 public BoxFile.Info uploadNewVersion( 897 InputStream fileContent, String fileContentSHA1, Date modified, String name) { 898 return this.uploadNewVersion(fileContent, fileContentSHA1, modified, name, 0, null); 899 } 900 901 /** 902 * Uploads a new version of this file, replacing the current version, while reporting the progress 903 * to a ProgressListener. Note that only users with premium accounts will be able to view and 904 * recover previous versions of the file. 905 * 906 * @param fileContent a stream containing the new file contents. 907 * @param modified the date that the new version was modified. 908 * @param fileSize the size of the file used for determining the progress of the upload. 909 * @param listener a listener for monitoring the upload's progress. 910 * @return the uploaded file version. 911 */ 912 public BoxFile.Info uploadNewVersion( 913 InputStream fileContent, Date modified, long fileSize, ProgressListener listener) { 914 return this.uploadNewVersion(fileContent, null, modified, fileSize, listener); 915 } 916 917 /** 918 * Uploads a new version of this file, replacing the current version, while reporting the progress 919 * to a ProgressListener. Note that only users with premium accounts will be able to view and 920 * recover previous versions of the file. 921 * 922 * @param fileContent a stream containing the new file contents. 923 * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the 924 * Content-MD5 header 925 * @param modified the date that the new version was modified. 926 * @param fileSize the size of the file used for determining the progress of the upload. 927 * @param listener a listener for monitoring the upload's progress. 928 * @return the uploaded file version. 929 */ 930 public BoxFile.Info uploadNewVersion( 931 InputStream fileContent, 932 String fileContentSHA1, 933 Date modified, 934 long fileSize, 935 ProgressListener listener) { 936 return this.uploadNewVersion(fileContent, fileContentSHA1, modified, null, fileSize, listener); 937 } 938 939 /** 940 * Uploads a new version of this file, replacing the current version, while reporting the progress 941 * to a ProgressListener. Note that only users with premium accounts will be able to view and 942 * recover previous versions of the file. 943 * 944 * @param fileContent a stream containing the new file contents. 945 * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the 946 * Content-MD5 header 947 * @param modified the date that the new version was modified. 948 * @param name the new name for the file 949 * @param fileSize the size of the file used for determining the progress of the upload. 950 * @param listener a listener for monitoring the upload's progress. 951 * @return the uploaded file version. 952 */ 953 public BoxFile.Info uploadNewVersion( 954 InputStream fileContent, 955 String fileContentSHA1, 956 Date modified, 957 String name, 958 long fileSize, 959 ProgressListener listener) { 960 URL uploadURL = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); 961 BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL); 962 963 if (fileSize > 0) { 964 request.setFile(fileContent, "", fileSize); 965 } else { 966 request.setFile(fileContent, ""); 967 } 968 969 if (fileContentSHA1 != null) { 970 request.setContentSHA1(fileContentSHA1); 971 } 972 973 JsonObject attributesJSON = new JsonObject(); 974 if (modified != null) { 975 attributesJSON.add("content_modified_at", BoxDateFormat.format(modified)); 976 } 977 978 if (name != null) { 979 attributesJSON.add("name", name); 980 } 981 982 request.putField("attributes", attributesJSON.toString()); 983 984 BoxJSONResponse response = null; 985 try { 986 if (listener == null) { 987 // upload is multipart request but response is JSON 988 response = (BoxJSONResponse) request.send(); 989 } else { 990 // upload is multipart request but response is JSON 991 response = (BoxJSONResponse) request.send(listener); 992 } 993 994 String fileJSON = response.getJsonObject().get("entries").asArray().get(0).toString(); 995 996 return new BoxFile.Info(fileJSON); 997 } finally { 998 Optional.ofNullable(response).ifPresent(BoxAPIResponse::close); 999 } 1000 } 1001 1002 /** 1003 * Gets an expiring URL for creating an embedded preview session. The URL will expire after 60 1004 * seconds and the preview session will expire after 60 minutes. 1005 * 1006 * @return the expiring preview link 1007 */ 1008 public URL getPreviewLink() { 1009 BoxFile.Info info = this.getInfo("expiring_embed_link"); 1010 1011 return info.getPreviewLink(); 1012 } 1013 1014 /** 1015 * Gets a list of any comments on this file. 1016 * 1017 * @return a list of comments on this file. 1018 */ 1019 public List<BoxComment.Info> getComments() { 1020 URL url = GET_COMMENTS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 1021 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); 1022 try (BoxJSONResponse response = request.send()) { 1023 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 1024 1025 int totalCount = responseJSON.get("total_count").asInt(); 1026 List<BoxComment.Info> comments = new ArrayList<>(totalCount); 1027 JsonArray entries = responseJSON.get("entries").asArray(); 1028 for (JsonValue value : entries) { 1029 JsonObject commentJSON = value.asObject(); 1030 BoxComment comment = new BoxComment(this.getAPI(), commentJSON.get("id").asString()); 1031 BoxComment.Info info = comment.new Info(commentJSON); 1032 comments.add(info); 1033 } 1034 1035 return comments; 1036 } 1037 } 1038 1039 /** 1040 * Gets a list of any tasks on this file with requested fields. 1041 * 1042 * @param fields optional fields to retrieve for this task. 1043 * @return a list of tasks on this file. 1044 */ 1045 public List<BoxTask.Info> getTasks(String... fields) { 1046 QueryStringBuilder builder = new QueryStringBuilder(); 1047 if (fields.length > 0) { 1048 builder.appendParam("fields", fields); 1049 } 1050 URL url = 1051 GET_TASKS_URL_TEMPLATE.buildWithQuery( 1052 this.getAPI().getBaseURL(), builder.toString(), this.getID()); 1053 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); 1054 try (BoxJSONResponse response = request.send()) { 1055 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 1056 1057 int totalCount = responseJSON.get("total_count").asInt(); 1058 List<BoxTask.Info> tasks = new ArrayList<>(totalCount); 1059 JsonArray entries = responseJSON.get("entries").asArray(); 1060 for (JsonValue value : entries) { 1061 JsonObject taskJSON = value.asObject(); 1062 BoxTask task = new BoxTask(this.getAPI(), taskJSON.get("id").asString()); 1063 BoxTask.Info info = task.new Info(taskJSON); 1064 tasks.add(info); 1065 } 1066 1067 return tasks; 1068 } 1069 } 1070 1071 /** 1072 * Creates metadata on this file in the global properties template. 1073 * 1074 * @param metadata The new metadata values. 1075 * @return the metadata returned from the server. 1076 */ 1077 public Metadata createMetadata(Metadata metadata) { 1078 return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata); 1079 } 1080 1081 /** 1082 * Creates metadata on this file in the specified template type. 1083 * 1084 * @param typeName the metadata template type name. 1085 * @param metadata the new metadata values. 1086 * @return the metadata returned from the server. 1087 */ 1088 public Metadata createMetadata(String typeName, Metadata metadata) { 1089 String scope = Metadata.scopeBasedOnType(typeName); 1090 return this.createMetadata(typeName, scope, metadata); 1091 } 1092 1093 /** 1094 * Creates metadata on this file in the specified template type. 1095 * 1096 * @param typeName the metadata template type name. 1097 * @param scope the metadata scope (global or enterprise). 1098 * @param metadata the new metadata values. 1099 * @return the metadata returned from the server. 1100 */ 1101 public Metadata createMetadata(String typeName, String scope, Metadata metadata) { 1102 URL url = 1103 METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName); 1104 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); 1105 request.setBody(metadata.toString()); 1106 try (BoxJSONResponse response = request.send()) { 1107 return new Metadata(Json.parse(response.getJSON()).asObject()); 1108 } 1109 } 1110 1111 /** 1112 * Sets the provided metadata on the file. If metadata has already been created on this file, it 1113 * overwrites metadata keys specified in the `metadata` param. 1114 * 1115 * @param templateName the name of the metadata template. 1116 * @param scope the scope of the template (usually "global" or "enterprise"). 1117 * @param metadata the new metadata values. 1118 * @return the metadata returned from the server. 1119 */ 1120 public Metadata setMetadata(String templateName, String scope, Metadata metadata) { 1121 try { 1122 return this.createMetadata(templateName, scope, metadata); 1123 } catch (BoxAPIException e) { 1124 if (e.getResponseCode() == 409) { 1125 if (metadata.getOperations().isEmpty()) { 1126 return getMetadata(); 1127 } else { 1128 return updateExistingTemplate(templateName, scope, metadata); 1129 } 1130 } else { 1131 throw e; 1132 } 1133 } 1134 } 1135 1136 private Metadata updateExistingTemplate(String templateName, String scope, Metadata metadata) { 1137 Metadata metadataToUpdate = new Metadata(scope, templateName); 1138 for (JsonValue value : metadata.getOperations()) { 1139 if (value.asObject().get("value").isNumber()) { 1140 metadataToUpdate.add( 1141 value.asObject().get("path").asString(), value.asObject().get("value").asDouble()); 1142 } else if (value.asObject().get("value").isString()) { 1143 metadataToUpdate.add( 1144 value.asObject().get("path").asString(), value.asObject().get("value").asString()); 1145 } else if (value.asObject().get("value").isArray()) { 1146 ArrayList<String> list = new ArrayList<>(); 1147 for (JsonValue jsonValue : value.asObject().get("value").asArray()) { 1148 list.add(jsonValue.asString()); 1149 } 1150 metadataToUpdate.add(value.asObject().get("path").asString(), list); 1151 } 1152 } 1153 return this.updateMetadata(metadataToUpdate); 1154 } 1155 1156 /** 1157 * Adds a metadata classification to the specified file. 1158 * 1159 * @param classificationType the metadata classification type. 1160 * @return the metadata classification type added to the file. 1161 */ 1162 public String addClassification(String classificationType) { 1163 Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType); 1164 Metadata classification = 1165 this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise", metadata); 1166 1167 return classification.getString(Metadata.CLASSIFICATION_KEY); 1168 } 1169 1170 /** 1171 * Updates a metadata classification on the specified file. 1172 * 1173 * @param classificationType the metadata classification type. 1174 * @return the new metadata classification type updated on the file. 1175 */ 1176 public String updateClassification(String classificationType) { 1177 Metadata metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY); 1178 metadata.add("/Box__Security__Classification__Key", classificationType); 1179 Metadata classification = this.updateMetadata(metadata); 1180 1181 return classification.getString(Metadata.CLASSIFICATION_KEY); 1182 } 1183 1184 /** 1185 * Attempts to add classification to a file. If classification already exists then do update. 1186 * 1187 * @param classificationType the metadata classification type. 1188 * @return the metadata classification type on the file. 1189 */ 1190 public String setClassification(String classificationType) { 1191 Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType); 1192 Metadata classification; 1193 1194 try { 1195 classification = 1196 this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise", metadata); 1197 } catch (BoxAPIException e) { 1198 if (e.getResponseCode() == 409) { 1199 metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY); 1200 metadata.replace(Metadata.CLASSIFICATION_KEY, classificationType); 1201 classification = this.updateMetadata(metadata); 1202 } else { 1203 throw e; 1204 } 1205 } 1206 1207 return classification.getString(Metadata.CLASSIFICATION_KEY); 1208 } 1209 1210 /** 1211 * Gets the classification type for the specified file. 1212 * 1213 * @return the metadata classification type on the file. 1214 */ 1215 public String getClassification() { 1216 Metadata metadata; 1217 try { 1218 metadata = this.getMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY); 1219 1220 } catch (BoxAPIException e) { 1221 JsonObject responseObject = Json.parse(e.getResponse()).asObject(); 1222 String code = responseObject.get("code").asString(); 1223 1224 if (e.getResponseCode() == 404 && code.equals("instance_not_found")) { 1225 return null; 1226 } else { 1227 throw e; 1228 } 1229 } 1230 1231 return metadata.getString(Metadata.CLASSIFICATION_KEY); 1232 } 1233 1234 /** Deletes the classification on the file. */ 1235 public void deleteClassification() { 1236 this.deleteMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise"); 1237 } 1238 1239 /** 1240 * Locks a file. 1241 * 1242 * @return the lock returned from the server. 1243 */ 1244 public BoxLock lock() { 1245 return this.lock(null, false); 1246 } 1247 1248 /** 1249 * Locks a file. 1250 * 1251 * @param isDownloadPrevented is downloading of file prevented when locked. 1252 * @return the lock returned from the server. 1253 */ 1254 public BoxLock lock(boolean isDownloadPrevented) { 1255 return this.lock(null, isDownloadPrevented); 1256 } 1257 1258 /** 1259 * Locks a file. 1260 * 1261 * @param expiresAt expiration date of the lock. 1262 * @return the lock returned from the server. 1263 */ 1264 public BoxLock lock(Date expiresAt) { 1265 return this.lock(expiresAt, false); 1266 } 1267 1268 /** 1269 * Locks a file. 1270 * 1271 * @param expiresAt expiration date of the lock. 1272 * @param isDownloadPrevented is downloading of file prevented when locked. 1273 * @return the lock returned from the server. 1274 */ 1275 public BoxLock lock(Date expiresAt, boolean isDownloadPrevented) { 1276 String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString(); 1277 URL url = 1278 FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 1279 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); 1280 1281 JsonObject lockConfig = new JsonObject(); 1282 lockConfig.add("type", "lock"); 1283 if (expiresAt != null) { 1284 lockConfig.add("expires_at", BoxDateFormat.format(expiresAt)); 1285 } 1286 lockConfig.add("is_download_prevented", isDownloadPrevented); 1287 1288 JsonObject requestJSON = new JsonObject(); 1289 requestJSON.add("lock", lockConfig); 1290 request.setBody(requestJSON.toString()); 1291 1292 try (BoxJSONResponse response = request.send()) { 1293 1294 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 1295 JsonValue lockValue = responseJSON.get("lock"); 1296 JsonObject lockJSON = Json.parse(lockValue.toString()).asObject(); 1297 1298 return new BoxLock(lockJSON, this.getAPI()); 1299 } 1300 } 1301 1302 /** Unlocks a file. */ 1303 public void unlock() { 1304 String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString(); 1305 URL url = 1306 FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 1307 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT"); 1308 1309 JsonObject lockObject = new JsonObject(); 1310 lockObject.add("lock", NULL); 1311 1312 request.setBody(lockObject.toString()); 1313 request.send().close(); 1314 } 1315 1316 /** 1317 * Used to retrieve all metadata associated with the file. 1318 * 1319 * @param fields the optional fields to retrieve. 1320 * @return An iterable of metadata instances associated with the file. 1321 */ 1322 public Iterable<Metadata> getAllMetadata(String... fields) { 1323 return Metadata.getAllMetadata(this, fields); 1324 } 1325 1326 /** 1327 * Gets the file properties metadata. 1328 * 1329 * @return the metadata returned from the server. 1330 */ 1331 public Metadata getMetadata() { 1332 return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE); 1333 } 1334 1335 /** 1336 * Gets the file metadata of specified template type. 1337 * 1338 * @param typeName the metadata template type name. 1339 * @return the metadata returned from the server. 1340 */ 1341 public Metadata getMetadata(String typeName) { 1342 String scope = Metadata.scopeBasedOnType(typeName); 1343 return this.getMetadata(typeName, scope); 1344 } 1345 1346 /** 1347 * Gets the file metadata of specified template type. 1348 * 1349 * @param typeName the metadata template type name. 1350 * @param scope the metadata scope (global or enterprise). 1351 * @return the metadata returned from the server. 1352 */ 1353 public Metadata getMetadata(String typeName, String scope) { 1354 URL url = 1355 METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName); 1356 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); 1357 try (BoxJSONResponse response = request.send()) { 1358 return new Metadata(Json.parse(response.getJSON()).asObject()); 1359 } 1360 } 1361 1362 /** 1363 * Updates the file metadata. 1364 * 1365 * @param metadata the new metadata values. 1366 * @return the metadata returned from the server. 1367 */ 1368 public Metadata updateMetadata(Metadata metadata) { 1369 String scope; 1370 if (metadata.getScope().equals(Metadata.GLOBAL_METADATA_SCOPE)) { 1371 scope = Metadata.GLOBAL_METADATA_SCOPE; 1372 } else if (metadata.getScope().startsWith(Metadata.ENTERPRISE_METADATA_SCOPE)) { 1373 scope = metadata.getScope(); 1374 } else { 1375 scope = Metadata.ENTERPRISE_METADATA_SCOPE; 1376 } 1377 1378 URL url = 1379 METADATA_URL_TEMPLATE.buildAlpha( 1380 this.getAPI().getBaseURL(), this.getID(), scope, metadata.getTemplateName()); 1381 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT", APPLICATION_JSON_PATCH); 1382 request.setBody(metadata.getPatch()); 1383 try (BoxJSONResponse response = request.send()) { 1384 return new Metadata(Json.parse(response.getJSON()).asObject()); 1385 } 1386 } 1387 1388 /** Deletes the file properties metadata. */ 1389 public void deleteMetadata() { 1390 this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE); 1391 } 1392 1393 /** 1394 * Deletes the file metadata of specified template type. 1395 * 1396 * @param typeName the metadata template type name. 1397 */ 1398 public void deleteMetadata(String typeName) { 1399 String scope = Metadata.scopeBasedOnType(typeName); 1400 this.deleteMetadata(typeName, scope); 1401 } 1402 1403 /** 1404 * Deletes the file metadata of specified template type. 1405 * 1406 * @param typeName the metadata template type name. 1407 * @param scope the metadata scope (global or enterprise). 1408 */ 1409 public void deleteMetadata(String typeName, String scope) { 1410 URL url = 1411 METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName); 1412 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE"); 1413 request.send().close(); 1414 } 1415 1416 /** 1417 * Used to retrieve the watermark for the file. If the file does not have a watermark applied to 1418 * it, a 404 Not Found will be returned by API. 1419 * 1420 * @param fields the fields to retrieve. 1421 * @return the watermark associated with the file. 1422 */ 1423 public BoxWatermark getWatermark(String... fields) { 1424 return this.getWatermark(FILE_URL_TEMPLATE, fields); 1425 } 1426 1427 /** 1428 * Used to apply or update the watermark for the file. 1429 * 1430 * @return the watermark associated with the file. 1431 */ 1432 public BoxWatermark applyWatermark() { 1433 return this.applyWatermark(FILE_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT); 1434 } 1435 1436 /** 1437 * Removes a watermark from the file. If the file did not have a watermark applied to it, a 404 1438 * Not Found will be returned by API. 1439 */ 1440 public void removeWatermark() { 1441 this.removeWatermark(FILE_URL_TEMPLATE); 1442 } 1443 1444 /** {@inheritDoc} */ 1445 @Override 1446 public BoxFile.Info setCollections(BoxCollection... collections) { 1447 JsonArray jsonArray = new JsonArray(); 1448 for (BoxCollection collection : collections) { 1449 JsonObject collectionJSON = new JsonObject(); 1450 collectionJSON.add("id", collection.getID()); 1451 jsonArray.add(collectionJSON); 1452 } 1453 JsonObject infoJSON = new JsonObject(); 1454 infoJSON.add("collections", jsonArray); 1455 1456 String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString(); 1457 URL url = 1458 FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 1459 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); 1460 request.setBody(infoJSON.toString()); 1461 try (BoxJSONResponse response = request.send()) { 1462 JsonObject jsonObject = Json.parse(response.getJSON()).asObject(); 1463 return new Info(jsonObject); 1464 } 1465 } 1466 1467 /** 1468 * Creates an upload session to create a new version of a file in chunks. This will first verify 1469 * that the version can be created and then open a session for uploading pieces of the file. 1470 * 1471 * @param fileSize the size of the file that will be uploaded. 1472 * @return the created upload session instance. 1473 */ 1474 public BoxFileUploadSession.Info createUploadSession(long fileSize) { 1475 URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); 1476 1477 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); 1478 request.addHeader("Content-Type", APPLICATION_JSON); 1479 1480 JsonObject body = new JsonObject(); 1481 body.add("file_size", fileSize); 1482 request.setBody(body.toString()); 1483 1484 try (BoxJSONResponse response = request.send()) { 1485 JsonObject jsonObject = Json.parse(response.getJSON()).asObject(); 1486 1487 String sessionId = jsonObject.get("id").asString(); 1488 BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId); 1489 return session.new Info(jsonObject); 1490 } 1491 } 1492 1493 /** 1494 * Creates a new version of a file. 1495 * 1496 * @param inputStream the stream instance that contains the data. 1497 * @param fileSize the size of the file that will be uploaded. 1498 * @return the created file instance. 1499 * @throws InterruptedException when a thread execution is interrupted. 1500 * @throws IOException when reading a stream throws exception. 1501 */ 1502 public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize) 1503 throws InterruptedException, IOException { 1504 URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); 1505 return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize); 1506 } 1507 1508 /** 1509 * Creates a new version of a file. Also sets file attributes. 1510 * 1511 * @param inputStream the stream instance that contains the data. 1512 * @param fileSize the size of the file that will be uploaded. 1513 * @param fileAttributes file attributes to set 1514 * @return the created file instance. 1515 * @throws InterruptedException when a thread execution is interrupted. 1516 * @throws IOException when reading a stream throws exception. 1517 */ 1518 public BoxFile.Info uploadLargeFile( 1519 InputStream inputStream, long fileSize, Map<String, String> fileAttributes) 1520 throws InterruptedException, IOException { 1521 URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); 1522 return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize, fileAttributes); 1523 } 1524 1525 /** 1526 * Creates a new version of a file using specified number of parallel http connections. 1527 * 1528 * @param inputStream the stream instance that contains the data. 1529 * @param fileSize the size of the file that will be uploaded. 1530 * @param nParallelConnections number of parallel http connections to use 1531 * @param timeOut time to wait before killing the job 1532 * @param unit time unit for the time wait value 1533 * @return the created file instance. 1534 * @throws InterruptedException when a thread execution is interrupted. 1535 * @throws IOException when reading a stream throws exception. 1536 */ 1537 public BoxFile.Info uploadLargeFile( 1538 InputStream inputStream, long fileSize, int nParallelConnections, long timeOut, TimeUnit unit) 1539 throws InterruptedException, IOException { 1540 URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); 1541 return new LargeFileUpload(nParallelConnections, timeOut, unit) 1542 .upload(this.getAPI(), inputStream, url, fileSize); 1543 } 1544 1545 /** 1546 * Creates a new version of a file using specified number of parallel http connections. Also sets 1547 * file attributes. 1548 * 1549 * @param inputStream the stream instance that contains the data. 1550 * @param fileSize the size of the file that will be uploaded. 1551 * @param nParallelConnections number of parallel http connections to use 1552 * @param timeOut time to wait before killing the job 1553 * @param unit time unit for the time wait value 1554 * @param fileAttributes file attributes to set 1555 * @return the created file instance. 1556 * @throws InterruptedException when a thread execution is interrupted. 1557 * @throws IOException when reading a stream throws exception. 1558 */ 1559 public BoxFile.Info uploadLargeFile( 1560 InputStream inputStream, 1561 long fileSize, 1562 int nParallelConnections, 1563 long timeOut, 1564 TimeUnit unit, 1565 Map<String, String> fileAttributes) 1566 throws InterruptedException, IOException { 1567 URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); 1568 return new LargeFileUpload(nParallelConnections, timeOut, unit) 1569 .upload(this.getAPI(), inputStream, url, fileSize, fileAttributes); 1570 } 1571 1572 private BoxCollaboration.Info collaborate( 1573 JsonObject accessibleByField, 1574 BoxCollaboration.Role role, 1575 Boolean notify, 1576 Boolean canViewPath, 1577 Date expiresAt, 1578 Boolean isAccessOnly) { 1579 1580 JsonObject itemField = new JsonObject(); 1581 itemField.add("id", this.getID()); 1582 itemField.add("type", "file"); 1583 1584 return BoxCollaboration.create( 1585 this.getAPI(), 1586 accessibleByField, 1587 itemField, 1588 role, 1589 notify, 1590 canViewPath, 1591 expiresAt, 1592 isAccessOnly); 1593 } 1594 1595 /** 1596 * Adds a collaborator to this file. 1597 * 1598 * @param collaborator the collaborator to add. 1599 * @param role the role of the collaborator. 1600 * @param notify determines if the user (or all the users in the group) will receive email 1601 * notifications. 1602 * @param canViewPath whether view path collaboration feature is enabled or not. 1603 * @param expiresAt when the collaboration should expire. 1604 * @param isAccessOnly whether the collaboration is access only or not. 1605 * @return info about the new collaboration. 1606 */ 1607 public BoxCollaboration.Info collaborate( 1608 BoxCollaborator collaborator, 1609 BoxCollaboration.Role role, 1610 Boolean notify, 1611 Boolean canViewPath, 1612 Date expiresAt, 1613 Boolean isAccessOnly) { 1614 JsonObject accessibleByField = new JsonObject(); 1615 accessibleByField.add("id", collaborator.getID()); 1616 1617 if (collaborator instanceof BoxUser) { 1618 accessibleByField.add("type", "user"); 1619 } else if (collaborator instanceof BoxGroup) { 1620 accessibleByField.add("type", "group"); 1621 } else { 1622 throw new IllegalArgumentException("The given collaborator is of an unknown type."); 1623 } 1624 return this.collaborate(accessibleByField, role, notify, canViewPath, expiresAt, isAccessOnly); 1625 } 1626 1627 /** 1628 * Adds a collaborator to this file. 1629 * 1630 * @param collaborator the collaborator to add. 1631 * @param role the role of the collaborator. 1632 * @param notify determines if the user (or all the users in the group) will receive email 1633 * notifications. 1634 * @param canViewPath whether view path collaboration feature is enabled or not. 1635 * @return info about the new collaboration. 1636 */ 1637 public BoxCollaboration.Info collaborate( 1638 BoxCollaborator collaborator, 1639 BoxCollaboration.Role role, 1640 Boolean notify, 1641 Boolean canViewPath) { 1642 return this.collaborate(collaborator, role, notify, canViewPath, null, null); 1643 } 1644 1645 /** 1646 * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't 1647 * already have a Box account. 1648 * 1649 * @param email the email address of the collaborator to add. 1650 * @param role the role of the collaborator. 1651 * @param notify determines if the user (or all the users in the group) will receive email 1652 * notifications. 1653 * @param canViewPath whether view path collaboration feature is enabled or not. 1654 * @param expiresAt when the collaboration should expire. 1655 * @param isAccessOnly whether the collaboration is access only or not. 1656 * @return info about the new collaboration. 1657 */ 1658 public BoxCollaboration.Info collaborate( 1659 String email, 1660 BoxCollaboration.Role role, 1661 Boolean notify, 1662 Boolean canViewPath, 1663 Date expiresAt, 1664 Boolean isAccessOnly) { 1665 JsonObject accessibleByField = new JsonObject(); 1666 accessibleByField.add("login", email); 1667 accessibleByField.add("type", "user"); 1668 1669 return this.collaborate(accessibleByField, role, notify, canViewPath, expiresAt, isAccessOnly); 1670 } 1671 1672 /** 1673 * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't 1674 * already have a Box account. 1675 * 1676 * @param email the email address of the collaborator to add. 1677 * @param role the role of the collaborator. 1678 * @param notify determines if the user (or all the users in the group) will receive email 1679 * notifications. 1680 * @param canViewPath whether view path collaboration feature is enabled or not. 1681 * @return info about the new collaboration. 1682 */ 1683 public BoxCollaboration.Info collaborate( 1684 String email, BoxCollaboration.Role role, Boolean notify, Boolean canViewPath) { 1685 return this.collaborate(email, role, notify, canViewPath, null, null); 1686 } 1687 1688 /** 1689 * Used to retrieve all collaborations associated with the item. 1690 * 1691 * @param fields the optional fields to retrieve. 1692 * @return An iterable of metadata instances associated with the item. 1693 */ 1694 public BoxResourceIterable<BoxCollaboration.Info> getAllFileCollaborations(String... fields) { 1695 return BoxCollaboration.getAllFileCollaborations( 1696 this.getAPI(), this.getID(), GET_COLLABORATORS_PAGE_SIZE, fields); 1697 } 1698 1699 /** Used to specify what filetype to request for a file thumbnail. */ 1700 public enum ThumbnailFileType { 1701 /** PNG image format. */ 1702 PNG, 1703 1704 /** JPG image format. */ 1705 JPG 1706 } 1707 1708 /** Enumerates the possible permissions that a user can have on a file. */ 1709 public enum Permission { 1710 /** The user can download the file. */ 1711 CAN_DOWNLOAD("can_download"), 1712 1713 /** The user can upload new versions of the file. */ 1714 CAN_UPLOAD("can_upload"), 1715 1716 /** The user can rename the file. */ 1717 CAN_RENAME("can_rename"), 1718 1719 /** The user can delete the file. */ 1720 CAN_DELETE("can_delete"), 1721 1722 /** The user can share the file. */ 1723 CAN_SHARE("can_share"), 1724 1725 /** The user can set the access level for shared links to the file. */ 1726 CAN_SET_SHARE_ACCESS("can_set_share_access"), 1727 1728 /** The user can preview the file. */ 1729 CAN_PREVIEW("can_preview"), 1730 1731 /** The user can comment on the file. */ 1732 CAN_COMMENT("can_comment"), 1733 1734 /** The user can place annotations on this file. */ 1735 CAN_ANNOTATE("can_annotate"), 1736 1737 /** 1738 * The current user can invite new users to collaborate on this item, and the user can update 1739 * the role of a user already collaborated on this item. 1740 */ 1741 CAN_INVITE_COLLABORATOR("can_invite_collaborator"), 1742 1743 /** The user can view all annotations placed on this file. */ 1744 CAN_VIEW_ANNOTATIONS_ALL("can_view_annotations_all"), 1745 1746 /** The user can view annotations placed by themselves on this file. */ 1747 CAN_VIEW_ANNOTATIONS_SELF("can_view_annotations_self"); 1748 1749 private final String jsonValue; 1750 1751 Permission(String jsonValue) { 1752 this.jsonValue = jsonValue; 1753 } 1754 1755 static Permission fromJSONValue(String jsonValue) { 1756 return Permission.valueOf(jsonValue.toUpperCase(java.util.Locale.ROOT)); 1757 } 1758 1759 String toJSONValue() { 1760 return this.jsonValue; 1761 } 1762 } 1763 1764 /** Contains information about a BoxFile. */ 1765 public class Info extends BoxItem.Info { 1766 private String sha1; 1767 private String versionNumber; 1768 private long commentCount; 1769 private EnumSet<Permission> permissions; 1770 private String extension; 1771 private boolean isPackage; 1772 private BoxFileVersion version; 1773 private URL previewLink; 1774 private BoxLock lock; 1775 private boolean isWatermarked; 1776 private boolean isExternallyOwned; 1777 private Map<String, Map<String, Metadata>> metadataMap; 1778 private List<Representation> representations; 1779 private List<String> allowedInviteeRoles; 1780 private Boolean hasCollaborations; 1781 private String uploaderDisplayName; 1782 private BoxClassification classification; 1783 private Date dispositionAt; 1784 private boolean isAccessibleViaSharedLink; 1785 1786 /** Constructs an empty Info object. */ 1787 public Info() { 1788 super(); 1789 } 1790 1791 /** 1792 * Constructs an Info object by parsing information from a JSON string. 1793 * 1794 * @param json the JSON string to parse. 1795 */ 1796 public Info(String json) { 1797 super(json); 1798 } 1799 1800 /** 1801 * Constructs an Info object using an already parsed JSON object. 1802 * 1803 * @param jsonObject the parsed JSON object. 1804 */ 1805 public Info(JsonObject jsonObject) { 1806 super(jsonObject); 1807 } 1808 1809 @Override 1810 public BoxFile getResource() { 1811 return BoxFile.this; 1812 } 1813 1814 /** 1815 * Gets the SHA1 hash of the file. 1816 * 1817 * @return the SHA1 hash of the file. 1818 */ 1819 public String getSha1() { 1820 return this.sha1; 1821 } 1822 1823 /** 1824 * Gets the lock of the file. 1825 * 1826 * @return the lock of the file. 1827 */ 1828 public BoxLock getLock() { 1829 return this.lock; 1830 } 1831 1832 /** 1833 * Gets the current version number of the file. 1834 * 1835 * @return the current version number of the file. 1836 */ 1837 public String getVersionNumber() { 1838 return this.versionNumber; 1839 } 1840 1841 /** 1842 * Gets the number of comments on the file. 1843 * 1844 * @return the number of comments on the file. 1845 */ 1846 public long getCommentCount() { 1847 return this.commentCount; 1848 } 1849 1850 /** 1851 * Gets the permissions that the current user has on the file. 1852 * 1853 * @return the permissions that the current user has on the file. 1854 */ 1855 public EnumSet<Permission> getPermissions() { 1856 return this.permissions; 1857 } 1858 1859 /** 1860 * Gets the extension suffix of the file, excluding the dot. 1861 * 1862 * @return the extension of the file. 1863 */ 1864 public String getExtension() { 1865 return this.extension; 1866 } 1867 1868 /** 1869 * Gets whether or not the file is an OSX package. 1870 * 1871 * @return true if the file is an OSX package; otherwise false. 1872 */ 1873 public boolean getIsPackage() { 1874 return this.isPackage; 1875 } 1876 1877 /** 1878 * Gets the current version details of the file. 1879 * 1880 * @return the current version details of the file. 1881 */ 1882 public BoxFileVersion getVersion() { 1883 return this.version; 1884 } 1885 1886 /** 1887 * Gets the current expiring preview link. 1888 * 1889 * @return the expiring preview link 1890 */ 1891 public URL getPreviewLink() { 1892 return this.previewLink; 1893 } 1894 1895 /** 1896 * Gets flag indicating whether this file is Watermarked. 1897 * 1898 * @return whether the file is watermarked or not 1899 */ 1900 public boolean getIsWatermarked() { 1901 return this.isWatermarked; 1902 } 1903 1904 /** 1905 * Returns the allowed invitee roles for this file item. 1906 * 1907 * @return the list of roles allowed for invited collaborators. 1908 */ 1909 public List<String> getAllowedInviteeRoles() { 1910 return this.allowedInviteeRoles; 1911 } 1912 1913 /** 1914 * Returns the indicator for whether this file item has collaborations. 1915 * 1916 * @return indicator for whether this file item has collaborations. 1917 */ 1918 public Boolean getHasCollaborations() { 1919 return this.hasCollaborations; 1920 } 1921 1922 /** 1923 * Gets the metadata on this file associated with a specified scope and template. Makes an 1924 * attempt to get metadata that was retrieved using getInfo(String ...) method. 1925 * 1926 * @param templateName the metadata template type name. 1927 * @param scope the scope of the template (usually "global" or "enterprise"). 1928 * @return the metadata returned from the server. 1929 */ 1930 public Metadata getMetadata(String templateName, String scope) { 1931 try { 1932 return this.metadataMap.get(scope).get(templateName); 1933 } catch (NullPointerException e) { 1934 return null; 1935 } 1936 } 1937 1938 /** 1939 * Returns the field for indicating whether a file is owned by a user outside the enterprise. 1940 * 1941 * @return indicator for whether or not the file is owned by a user outside the enterprise. 1942 */ 1943 public boolean getIsExternallyOwned() { 1944 return this.isExternallyOwned; 1945 } 1946 1947 /** 1948 * Get file's representations. 1949 * 1950 * @return list of representations 1951 */ 1952 public List<Representation> getRepresentations() { 1953 return this.representations; 1954 } 1955 1956 /** 1957 * Returns user's name at the time of upload. 1958 * 1959 * @return user's name at the time of upload 1960 */ 1961 public String getUploaderDisplayName() { 1962 return this.uploaderDisplayName; 1963 } 1964 1965 /** 1966 * Gets the metadata classification type of this file. 1967 * 1968 * @return the metadata classification type of this file. 1969 */ 1970 public BoxClassification getClassification() { 1971 return this.classification; 1972 } 1973 1974 /** 1975 * Returns the retention expiration timestamp for the given file. 1976 * 1977 * @return Date representing expiration timestamp 1978 */ 1979 public Date getDispositionAt() { 1980 return dispositionAt; 1981 } 1982 1983 /** 1984 * Modifies the retention expiration timestamp for the given file. This date cannot be shortened 1985 * once set on a file. 1986 * 1987 * @param dispositionAt Date representing expiration timestamp 1988 */ 1989 public void setDispositionAt(Date dispositionAt) { 1990 this.dispositionAt = dispositionAt; 1991 this.addPendingChange("disposition_at", BoxDateFormat.format(dispositionAt)); 1992 } 1993 1994 /** 1995 * Returns the flag indicating whether the file is accessible via a shared link. 1996 * 1997 * @return boolean flag indicating whether the file is accessible via a shared link. 1998 */ 1999 public boolean getIsAccessibleViaSharedLink() { 2000 return this.isAccessibleViaSharedLink; 2001 } 2002 2003 @Override 2004 protected void parseJSONMember(JsonObject.Member member) { 2005 super.parseJSONMember(member); 2006 2007 String memberName = member.getName(); 2008 JsonValue value = member.getValue(); 2009 try { 2010 switch (memberName) { 2011 case "sha1": 2012 this.sha1 = value.asString(); 2013 break; 2014 case "version_number": 2015 this.versionNumber = value.asString(); 2016 break; 2017 case "comment_count": 2018 this.commentCount = value.asLong(); 2019 break; 2020 case "permissions": 2021 this.permissions = this.parsePermissions(value.asObject()); 2022 break; 2023 case "extension": 2024 this.extension = value.asString(); 2025 break; 2026 case "is_package": 2027 this.isPackage = value.asBoolean(); 2028 break; 2029 case "has_collaborations": 2030 this.hasCollaborations = value.asBoolean(); 2031 break; 2032 case "is_externally_owned": 2033 this.isExternallyOwned = value.asBoolean(); 2034 break; 2035 case "file_version": 2036 this.version = this.parseFileVersion(value.asObject()); 2037 break; 2038 case "allowed_invitee_roles": 2039 this.allowedInviteeRoles = this.parseAllowedInviteeRoles(value.asArray()); 2040 break; 2041 case "expiring_embed_link": 2042 try { 2043 String urlString = member.getValue().asObject().get("url").asString(); 2044 this.previewLink = new URL(urlString); 2045 } catch (MalformedURLException e) { 2046 throw new BoxAPIException("Couldn't parse expiring_embed_link/url for file", e); 2047 } 2048 break; 2049 case "lock": 2050 if (value.isNull()) { 2051 this.lock = null; 2052 } else { 2053 this.lock = new BoxLock(value.asObject(), BoxFile.this.getAPI()); 2054 } 2055 break; 2056 case "watermark_info": 2057 this.isWatermarked = value.asObject().get("is_watermarked").asBoolean(); 2058 break; 2059 case "metadata": 2060 this.metadataMap = Parsers.parseAndPopulateMetadataMap(value.asObject()); 2061 break; 2062 case "representations": 2063 this.representations = Parsers.parseRepresentations(value.asObject()); 2064 break; 2065 case "uploader_display_name": 2066 this.uploaderDisplayName = value.asString(); 2067 break; 2068 case "classification": 2069 if (value.isNull()) { 2070 this.classification = null; 2071 } else { 2072 this.classification = new BoxClassification(value.asObject()); 2073 } 2074 break; 2075 case "disposition_at": 2076 this.dispositionAt = BoxDateFormat.parse(value.asString()); 2077 break; 2078 case "is_accessible_via_shared_link": 2079 this.isAccessibleViaSharedLink = value.asBoolean(); 2080 break; 2081 default: 2082 break; 2083 } 2084 } catch (Exception e) { 2085 throw new BoxDeserializationException(memberName, value.toString(), e); 2086 } 2087 } 2088 2089 @SuppressWarnings("checkstyle:MissingSwitchDefault") 2090 private EnumSet<Permission> parsePermissions(JsonObject jsonObject) { 2091 EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class); 2092 for (JsonObject.Member member : jsonObject) { 2093 JsonValue value = member.getValue(); 2094 if (value.isNull() || !value.asBoolean()) { 2095 continue; 2096 } 2097 try { 2098 permissions.add(Permission.fromJSONValue(member.getName())); 2099 } catch (IllegalArgumentException ignored) { 2100 // If the permission is not recognized, we ignore it. 2101 } 2102 } 2103 2104 return permissions; 2105 } 2106 2107 private BoxFileVersion parseFileVersion(JsonObject jsonObject) { 2108 return new BoxFileVersion(BoxFile.this.getAPI(), jsonObject, BoxFile.this.getID()); 2109 } 2110 2111 private List<String> parseAllowedInviteeRoles(JsonArray jsonArray) { 2112 List<String> roles = new ArrayList<>(jsonArray.size()); 2113 for (JsonValue value : jsonArray) { 2114 roles.add(value.asString()); 2115 } 2116 2117 return roles; 2118 } 2119 } 2120}