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 * Adds a metadata classification to the specified file. 1043 * 1044 * @param classificationType the metadata classification type. 1045 * @return the metadata classification type added to the file. 1046 */ 1047 public String addClassification(String classificationType) { 1048 Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType); 1049 Metadata classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, 1050 "enterprise", metadata); 1051 1052 return classification.getString(Metadata.CLASSIFICATION_KEY); 1053 } 1054 1055 /** 1056 * Updates a metadata classification on the specified file. 1057 * 1058 * @param classificationType the metadata classification type. 1059 * @return the new metadata classification type updated on the file. 1060 */ 1061 public String updateClassification(String classificationType) { 1062 Metadata metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY); 1063 metadata.add("/Box__Security__Classification__Key", classificationType); 1064 Metadata classification = this.updateMetadata(metadata); 1065 1066 return classification.getString(Metadata.CLASSIFICATION_KEY); 1067 } 1068 1069 /** 1070 * Attempts to add classification to a file. If classification already exists then do update. 1071 * 1072 * @param classificationType the metadata classification type. 1073 * @return the metadata classification type on the file. 1074 */ 1075 public String setClassification(String classificationType) { 1076 Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType); 1077 Metadata classification = null; 1078 1079 try { 1080 classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise", metadata); 1081 } catch (BoxAPIException e) { 1082 if (e.getResponseCode() == 409) { 1083 metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY); 1084 metadata.replace(Metadata.CLASSIFICATION_KEY, classificationType); 1085 classification = this.updateMetadata(metadata); 1086 } else { 1087 throw e; 1088 } 1089 } 1090 1091 return classification.getString(Metadata.CLASSIFICATION_KEY); 1092 } 1093 1094 /** 1095 * Gets the classification type for the specified file. 1096 * 1097 * @return the metadata classification type on the file. 1098 */ 1099 public String getClassification() { 1100 Metadata metadata; 1101 try { 1102 metadata = this.getMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY); 1103 1104 } catch (BoxAPIException e) { 1105 JsonObject responseObject = JsonObject.readFrom(e.getResponse()); 1106 String code = responseObject.get("code").asString(); 1107 1108 if (e.getResponseCode() == 404 && code.equals("instance_not_found")) { 1109 return null; 1110 } else { 1111 throw e; 1112 } 1113 } 1114 1115 return metadata.getString(Metadata.CLASSIFICATION_KEY); 1116 } 1117 1118 /** 1119 * Deletes the classification on the file. 1120 */ 1121 public void deleteClassification() { 1122 this.deleteMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise"); 1123 } 1124 1125 /** 1126 * Locks a file. 1127 * 1128 * @return the lock returned from the server. 1129 */ 1130 public BoxLock lock() { 1131 return this.lock(null, false); 1132 } 1133 1134 /** 1135 * Locks a file. 1136 * 1137 * @param isDownloadPrevented is downloading of file prevented when locked. 1138 * @return the lock returned from the server. 1139 */ 1140 public BoxLock lock(boolean isDownloadPrevented) { 1141 return this.lock(null, isDownloadPrevented); 1142 } 1143 1144 /** 1145 * Locks a file. 1146 * 1147 * @param expiresAt expiration date of the lock. 1148 * @return the lock returned from the server. 1149 */ 1150 public BoxLock lock(Date expiresAt) { 1151 return this.lock(expiresAt, false); 1152 } 1153 1154 /** 1155 * Locks a file. 1156 * 1157 * @param expiresAt expiration date of the lock. 1158 * @param isDownloadPrevented is downloading of file prevented when locked. 1159 * @return the lock returned from the server. 1160 */ 1161 public BoxLock lock(Date expiresAt, boolean isDownloadPrevented) { 1162 String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString(); 1163 URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 1164 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT"); 1165 1166 JsonObject lockConfig = new JsonObject(); 1167 lockConfig.add("type", "lock"); 1168 if (expiresAt != null) { 1169 lockConfig.add("expires_at", BoxDateFormat.format(expiresAt)); 1170 } 1171 lockConfig.add("is_download_prevented", isDownloadPrevented); 1172 1173 JsonObject requestJSON = new JsonObject(); 1174 requestJSON.add("lock", lockConfig); 1175 request.setBody(requestJSON.toString()); 1176 1177 BoxJSONResponse response = (BoxJSONResponse) request.send(); 1178 1179 JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); 1180 JsonValue lockValue = responseJSON.get("lock"); 1181 JsonObject lockJSON = JsonObject.readFrom(lockValue.toString()); 1182 1183 return new BoxLock(lockJSON, this.getAPI()); 1184 } 1185 1186 /** 1187 * Unlocks a file. 1188 */ 1189 public void unlock() { 1190 String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString(); 1191 URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 1192 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT"); 1193 1194 JsonObject lockObject = new JsonObject(); 1195 lockObject.add("lock", JsonObject.NULL); 1196 1197 request.setBody(lockObject.toString()); 1198 request.send(); 1199 } 1200 1201 /** 1202 * Used to retrieve all metadata associated with the file. 1203 * 1204 * @param fields the optional fields to retrieve. 1205 * @return An iterable of metadata instances associated with the file. 1206 */ 1207 public Iterable<Metadata> getAllMetadata(String... fields) { 1208 return Metadata.getAllMetadata(this, fields); 1209 } 1210 1211 /** 1212 * Gets the file properties metadata. 1213 * 1214 * @return the metadata returned from the server. 1215 */ 1216 public Metadata getMetadata() { 1217 return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE); 1218 } 1219 1220 /** 1221 * Gets the file metadata of specified template type. 1222 * 1223 * @param typeName the metadata template type name. 1224 * @return the metadata returned from the server. 1225 */ 1226 public Metadata getMetadata(String typeName) { 1227 String scope = Metadata.scopeBasedOnType(typeName); 1228 return this.getMetadata(typeName, scope); 1229 } 1230 1231 /** 1232 * Gets the file metadata of specified template type. 1233 * 1234 * @param typeName the metadata template type name. 1235 * @param scope the metadata scope (global or enterprise). 1236 * @return the metadata returned from the server. 1237 */ 1238 public Metadata getMetadata(String typeName, String scope) { 1239 URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, typeName); 1240 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 1241 BoxJSONResponse response = (BoxJSONResponse) request.send(); 1242 return new Metadata(JsonObject.readFrom(response.getJSON())); 1243 } 1244 1245 /** 1246 * Updates the file metadata. 1247 * 1248 * @param metadata the new metadata values. 1249 * @return the metadata returned from the server. 1250 */ 1251 public Metadata updateMetadata(Metadata metadata) { 1252 String scope; 1253 if (metadata.getScope().equals(Metadata.GLOBAL_METADATA_SCOPE)) { 1254 scope = Metadata.GLOBAL_METADATA_SCOPE; 1255 } else { 1256 scope = Metadata.ENTERPRISE_METADATA_SCOPE; 1257 } 1258 1259 URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), 1260 scope, metadata.getTemplateName()); 1261 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT"); 1262 request.addHeader("Content-Type", "application/json-patch+json"); 1263 request.setBody(metadata.getPatch()); 1264 BoxJSONResponse response = (BoxJSONResponse) request.send(); 1265 return new Metadata(JsonObject.readFrom(response.getJSON())); 1266 } 1267 1268 /** 1269 * Deletes the file properties metadata. 1270 */ 1271 public void deleteMetadata() { 1272 this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE); 1273 } 1274 1275 /** 1276 * Deletes the file metadata of specified template type. 1277 * 1278 * @param typeName the metadata template type name. 1279 */ 1280 public void deleteMetadata(String typeName) { 1281 String scope = Metadata.scopeBasedOnType(typeName); 1282 this.deleteMetadata(typeName, scope); 1283 } 1284 1285 /** 1286 * Deletes the file metadata of specified template type. 1287 * 1288 * @param typeName the metadata template type name. 1289 * @param scope the metadata scope (global or enterprise). 1290 */ 1291 public void deleteMetadata(String typeName, String scope) { 1292 URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, typeName); 1293 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE"); 1294 request.send(); 1295 } 1296 1297 /** 1298 * Used to retrieve the watermark for the file. 1299 * If the file does not have a watermark applied to it, a 404 Not Found will be returned by API. 1300 * 1301 * @param fields the fields to retrieve. 1302 * @return the watermark associated with the file. 1303 */ 1304 public BoxWatermark getWatermark(String... fields) { 1305 return this.getWatermark(FILE_URL_TEMPLATE, fields); 1306 } 1307 1308 /** 1309 * Used to apply or update the watermark for the file. 1310 * 1311 * @return the watermark associated with the file. 1312 */ 1313 public BoxWatermark applyWatermark() { 1314 return this.applyWatermark(FILE_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT); 1315 } 1316 1317 /** 1318 * Removes a watermark from the file. 1319 * If the file did not have a watermark applied to it, a 404 Not Found will be returned by API. 1320 */ 1321 public void removeWatermark() { 1322 this.removeWatermark(FILE_URL_TEMPLATE); 1323 } 1324 1325 /** 1326 * {@inheritDoc} 1327 */ 1328 @Override 1329 public BoxFile.Info setCollections(BoxCollection... collections) { 1330 JsonArray jsonArray = new JsonArray(); 1331 for (BoxCollection collection : collections) { 1332 JsonObject collectionJSON = new JsonObject(); 1333 collectionJSON.add("id", collection.getID()); 1334 jsonArray.add(collectionJSON); 1335 } 1336 JsonObject infoJSON = new JsonObject(); 1337 infoJSON.add("collections", jsonArray); 1338 1339 String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString(); 1340 URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 1341 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); 1342 request.setBody(infoJSON.toString()); 1343 BoxJSONResponse response = (BoxJSONResponse) request.send(); 1344 JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); 1345 return new Info(jsonObject); 1346 } 1347 1348 /** 1349 * Creates an upload session to create a new version of a file in chunks. 1350 * This will first verify that the version can be created and then open a session for uploading pieces of the file. 1351 * @param fileSize the size of the file that will be uploaded. 1352 * @return the created upload session instance. 1353 */ 1354 public BoxFileUploadSession.Info createUploadSession(long fileSize) { 1355 URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); 1356 1357 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); 1358 request.addHeader("Content-Type", "application/json"); 1359 1360 JsonObject body = new JsonObject(); 1361 body.add("file_size", fileSize); 1362 request.setBody(body.toString()); 1363 1364 BoxJSONResponse response = (BoxJSONResponse) request.send(); 1365 JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); 1366 1367 String sessionId = jsonObject.get("id").asString(); 1368 BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId); 1369 return session.new Info(jsonObject); 1370 } 1371 1372 /** 1373 * Creates a new version of a file. 1374 * @param inputStream the stream instance that contains the data. 1375 * @param fileSize the size of the file that will be uploaded. 1376 * @return the created file instance. 1377 * @throws InterruptedException when a thread execution is interrupted. 1378 * @throws IOException when reading a stream throws exception. 1379 */ 1380 public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize) 1381 throws InterruptedException, IOException { 1382 URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); 1383 return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize); 1384 } 1385 1386 /** 1387 * Creates a new version of a file using specified number of parallel http connections. 1388 * @param inputStream the stream instance that contains the data. 1389 * @param fileSize the size of the file that will be uploaded. 1390 * @param nParallelConnections number of parallel http connections to use 1391 * @param timeOut time to wait before killing the job 1392 * @param unit time unit for the time wait value 1393 * @return the created file instance. 1394 * @throws InterruptedException when a thread execution is interrupted. 1395 * @throws IOException when reading a stream throws exception. 1396 */ 1397 public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize, 1398 int nParallelConnections, long timeOut, TimeUnit unit) 1399 throws InterruptedException, IOException { 1400 URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); 1401 return new LargeFileUpload(nParallelConnections, timeOut, unit) 1402 .upload(this.getAPI(), inputStream, url, fileSize); 1403 } 1404 1405 private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role, 1406 Boolean notify, Boolean canViewPath) { 1407 1408 JsonObject itemField = new JsonObject(); 1409 itemField.add("id", this.getID()); 1410 itemField.add("type", "file"); 1411 1412 return BoxCollaboration.create(this.getAPI(), accessibleByField, itemField, role, notify, canViewPath); 1413 } 1414 1415 /** 1416 * Adds a collaborator to this file. 1417 * 1418 * @param collaborator the collaborator to add. 1419 * @param role the role of the collaborator. 1420 * @param notify determines if the user (or all the users in the group) will receive email notifications. 1421 * @param canViewPath whether view path collaboration feature is enabled or not. 1422 * @return info about the new collaboration. 1423 */ 1424 public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role, 1425 Boolean notify, Boolean canViewPath) { 1426 JsonObject accessibleByField = new JsonObject(); 1427 accessibleByField.add("id", collaborator.getID()); 1428 1429 if (collaborator instanceof BoxUser) { 1430 accessibleByField.add("type", "user"); 1431 } else if (collaborator instanceof BoxGroup) { 1432 accessibleByField.add("type", "group"); 1433 } else { 1434 throw new IllegalArgumentException("The given collaborator is of an unknown type."); 1435 } 1436 return this.collaborate(accessibleByField, role, notify, canViewPath); 1437 } 1438 1439 1440 /** 1441 * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box 1442 * account. 1443 * 1444 * @param email the email address of the collaborator to add. 1445 * @param role the role of the collaborator. 1446 * @param notify determines if the user (or all the users in the group) will receive email notifications. 1447 * @param canViewPath whether view path collaboration feature is enabled or not. 1448 * @return info about the new collaboration. 1449 */ 1450 public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role, 1451 Boolean notify, Boolean canViewPath) { 1452 JsonObject accessibleByField = new JsonObject(); 1453 accessibleByField.add("login", email); 1454 accessibleByField.add("type", "user"); 1455 1456 return this.collaborate(accessibleByField, role, notify, canViewPath); 1457 } 1458 1459 /** 1460 * Used to retrieve all collaborations associated with the item. 1461 * 1462 * @param fields the optional fields to retrieve. 1463 * @return An iterable of metadata instances associated with the item. 1464 */ 1465 public BoxResourceIterable<BoxCollaboration.Info> getAllFileCollaborations(String... fields) { 1466 return BoxCollaboration.getAllFileCollaborations(this.getAPI(), this.getID(), 1467 GET_COLLABORATORS_PAGE_SIZE, fields); 1468 1469 } 1470 1471 /** 1472 * Contains information about a BoxFile. 1473 */ 1474 public class Info extends BoxItem.Info { 1475 private String sha1; 1476 private String versionNumber; 1477 private long commentCount; 1478 private EnumSet<Permission> permissions; 1479 private String extension; 1480 private boolean isPackage; 1481 private BoxFileVersion version; 1482 private URL previewLink; 1483 private BoxLock lock; 1484 private boolean isWatermarked; 1485 private JsonObject metadata; 1486 private Map<String, Map<String, Metadata>> metadataMap; 1487 private List<Representation> representations; 1488 1489 /** 1490 * Constructs an empty Info object. 1491 */ 1492 public Info() { 1493 super(); 1494 } 1495 1496 /** 1497 * Constructs an Info object by parsing information from a JSON string. 1498 * 1499 * @param json the JSON string to parse. 1500 */ 1501 public Info(String json) { 1502 super(json); 1503 } 1504 1505 /** 1506 * Constructs an Info object using an already parsed JSON object. 1507 * 1508 * @param jsonObject the parsed JSON object. 1509 */ 1510 public Info(JsonObject jsonObject) { 1511 super(jsonObject); 1512 } 1513 1514 @Override 1515 public BoxFile getResource() { 1516 return BoxFile.this; 1517 } 1518 1519 /** 1520 * Gets the SHA1 hash of the file. 1521 * 1522 * @return the SHA1 hash of the file. 1523 */ 1524 public String getSha1() { 1525 return this.sha1; 1526 } 1527 1528 /** 1529 * Gets the lock of the file. 1530 * 1531 * @return the lock of the file. 1532 */ 1533 public BoxLock getLock() { 1534 return this.lock; 1535 } 1536 1537 /** 1538 * Gets the current version number of the file. 1539 * 1540 * @return the current version number of the file. 1541 */ 1542 public String getVersionNumber() { 1543 return this.versionNumber; 1544 } 1545 1546 /** 1547 * Gets the number of comments on the file. 1548 * 1549 * @return the number of comments on the file. 1550 */ 1551 public long getCommentCount() { 1552 return this.commentCount; 1553 } 1554 1555 /** 1556 * Gets the permissions that the current user has on the file. 1557 * 1558 * @return the permissions that the current user has on the file. 1559 */ 1560 public EnumSet<Permission> getPermissions() { 1561 return this.permissions; 1562 } 1563 1564 /** 1565 * Gets the extension suffix of the file, excluding the dot. 1566 * 1567 * @return the extension of the file. 1568 */ 1569 public String getExtension() { 1570 return this.extension; 1571 } 1572 1573 /** 1574 * Gets whether or not the file is an OSX package. 1575 * 1576 * @return true if the file is an OSX package; otherwise false. 1577 */ 1578 public boolean getIsPackage() { 1579 return this.isPackage; 1580 } 1581 1582 /** 1583 * Gets the current version details of the file. 1584 * 1585 * @return the current version details of the file. 1586 */ 1587 public BoxFileVersion getVersion() { 1588 return this.version; 1589 } 1590 1591 /** 1592 * Gets the current expiring preview link. 1593 * 1594 * @return the expiring preview link 1595 */ 1596 public URL getPreviewLink() { 1597 return this.previewLink; 1598 } 1599 1600 /** 1601 * Gets flag indicating whether this file is Watermarked. 1602 * 1603 * @return whether the file is watermarked or not 1604 */ 1605 public boolean getIsWatermarked() { 1606 return this.isWatermarked; 1607 } 1608 1609 /** 1610 * Gets the metadata on this file associated with a specified scope and template. 1611 * Makes an attempt to get metadata that was retrieved using getInfo(String ...) method. If no result is found 1612 * then makes an API call to get metadata 1613 * @param templateName the metadata template type name. 1614 * @param scope the scope of the template (usually "global" or "enterprise"). 1615 * @return the metadata returned from the server. 1616 */ 1617 public Metadata getMetadata(String templateName, String scope) { 1618 try { 1619 return this.metadataMap.get(scope).get(templateName); 1620 } catch (NullPointerException e) { 1621 return null; 1622 } 1623 } 1624 1625 /** 1626 * Get file's representations. 1627 * @return list of representations 1628 */ 1629 public List<Representation> getRepresentations() { 1630 return this.representations; 1631 } 1632 1633 @Override 1634 protected void parseJSONMember(JsonObject.Member member) { 1635 super.parseJSONMember(member); 1636 1637 String memberName = member.getName(); 1638 JsonValue value = member.getValue(); 1639 if (memberName.equals("sha1")) { 1640 this.sha1 = value.asString(); 1641 } else if (memberName.equals("version_number")) { 1642 this.versionNumber = value.asString(); 1643 } else if (memberName.equals("comment_count")) { 1644 this.commentCount = value.asLong(); 1645 } else if (memberName.equals("permissions")) { 1646 this.permissions = this.parsePermissions(value.asObject()); 1647 } else if (memberName.equals("extension")) { 1648 this.extension = value.asString(); 1649 } else if (memberName.equals("is_package")) { 1650 this.isPackage = value.asBoolean(); 1651 } else if (memberName.equals("file_version")) { 1652 this.version = this.parseFileVersion(value.asObject()); 1653 } else if (memberName.equals("expiring_embed_link")) { 1654 try { 1655 String urlString = member.getValue().asObject().get("url").asString(); 1656 this.previewLink = new URL(urlString); 1657 } catch (MalformedURLException e) { 1658 throw new BoxAPIException("Couldn't parse expiring_embed_link/url for file", e); 1659 } 1660 } else if (memberName.equals("lock")) { 1661 if (value.isNull()) { 1662 this.lock = null; 1663 } else { 1664 this.lock = new BoxLock(value.asObject(), BoxFile.this.getAPI()); 1665 } 1666 } else if (memberName.equals("watermark_info")) { 1667 JsonObject jsonObject = value.asObject(); 1668 this.isWatermarked = jsonObject.get("is_watermarked").asBoolean(); 1669 } else if (memberName.equals("metadata")) { 1670 JsonObject jsonObject = value.asObject(); 1671 this.metadataMap = Parsers.parseAndPopulateMetadataMap(jsonObject); 1672 } else if (memberName.equals("representations")) { 1673 JsonObject jsonObject = value.asObject(); 1674 this.representations = Parsers.parseRepresentations(jsonObject); 1675 } 1676 } 1677 1678 private EnumSet<Permission> parsePermissions(JsonObject jsonObject) { 1679 EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class); 1680 for (JsonObject.Member member : jsonObject) { 1681 JsonValue value = member.getValue(); 1682 if (value.isNull() || !value.asBoolean()) { 1683 continue; 1684 } 1685 1686 String memberName = member.getName(); 1687 if (memberName.equals("can_download")) { 1688 permissions.add(Permission.CAN_DOWNLOAD); 1689 } else if (memberName.equals("can_upload")) { 1690 permissions.add(Permission.CAN_UPLOAD); 1691 } else if (memberName.equals("can_rename")) { 1692 permissions.add(Permission.CAN_RENAME); 1693 } else if (memberName.equals("can_delete")) { 1694 permissions.add(Permission.CAN_DELETE); 1695 } else if (memberName.equals("can_share")) { 1696 permissions.add(Permission.CAN_SHARE); 1697 } else if (memberName.equals("can_set_share_access")) { 1698 permissions.add(Permission.CAN_SET_SHARE_ACCESS); 1699 } else if (memberName.equals("can_preview")) { 1700 permissions.add(Permission.CAN_PREVIEW); 1701 } else if (memberName.equals("can_comment")) { 1702 permissions.add(Permission.CAN_COMMENT); 1703 } 1704 } 1705 1706 return permissions; 1707 } 1708 1709 private BoxFileVersion parseFileVersion(JsonObject jsonObject) { 1710 return new BoxFileVersion(BoxFile.this.getAPI(), jsonObject, BoxFile.this.getID()); 1711 } 1712 } 1713 1714 /** 1715 * Enumerates the possible permissions that a user can have on a file. 1716 */ 1717 public enum Permission { 1718 /** 1719 * The user can download the file. 1720 */ 1721 CAN_DOWNLOAD("can_download"), 1722 1723 /** 1724 * The user can upload new versions of the file. 1725 */ 1726 CAN_UPLOAD("can_upload"), 1727 1728 /** 1729 * The user can rename the file. 1730 */ 1731 CAN_RENAME("can_rename"), 1732 1733 /** 1734 * The user can delete the file. 1735 */ 1736 CAN_DELETE("can_delete"), 1737 1738 /** 1739 * The user can share the file. 1740 */ 1741 CAN_SHARE("can_share"), 1742 1743 /** 1744 * The user can set the access level for shared links to the file. 1745 */ 1746 CAN_SET_SHARE_ACCESS("can_set_share_access"), 1747 1748 /** 1749 * The user can preview the file. 1750 */ 1751 CAN_PREVIEW("can_preview"), 1752 1753 /** 1754 * The user can comment on the file. 1755 */ 1756 CAN_COMMENT("can_comment"); 1757 1758 private final String jsonValue; 1759 1760 private Permission(String jsonValue) { 1761 this.jsonValue = jsonValue; 1762 } 1763 1764 static Permission fromJSONValue(String jsonValue) { 1765 return Permission.valueOf(jsonValue.toUpperCase()); 1766 } 1767 1768 String toJSONValue() { 1769 return this.jsonValue; 1770 } 1771 } 1772 1773}