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