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