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