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