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.Collection; 011import java.util.Date; 012import java.util.EnumSet; 013import java.util.List; 014 015import com.eclipsesource.json.JsonArray; 016import com.eclipsesource.json.JsonObject; 017import com.eclipsesource.json.JsonValue; 018 019 020/** 021 * Represents an individual file on Box. This class can be used to download a file's contents, upload new versions, and 022 * perform other common file operations (move, copy, delete, etc.). 023 * 024 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked 025 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error 026 * handling for errors related to the Box REST API, you should capture this exception explicitly.</p> 027 */ 028@BoxResourceType("file") 029public class BoxFile extends BoxItem { 030 031 /** 032 * An array of all possible file fields that can be requested when calling {@link #getInfo()}. 033 */ 034 public static final String[] ALL_FIELDS = {"type", "id", "sequence_id", "etag", "sha1", "name", 035 "description", "size", "path_collection", "created_at", "modified_at", "trashed_at", "purged_at", 036 "content_created_at", "content_modified_at", "created_by", "modified_by", "owned_by", "shared_link", "parent", 037 "item_status", "version_number", "comment_count", "permissions", "tags", "lock", "extension", "is_package", 038 "file_version", "collections", "watermark_info"}; 039 040 /** 041 * Used to specify what filetype to request for a file thumbnail. 042 */ 043 public enum ThumbnailFileType { 044 /** 045 * PNG image format. 046 */ 047 PNG, 048 049 /** 050 * JPG image format. 051 */ 052 JPG 053 } 054 055 private static final URLTemplate FILE_URL_TEMPLATE = new URLTemplate("files/%s"); 056 private static final URLTemplate CONTENT_URL_TEMPLATE = new URLTemplate("files/%s/content"); 057 private static final URLTemplate VERSIONS_URL_TEMPLATE = new URLTemplate("files/%s/versions"); 058 private static final URLTemplate COPY_URL_TEMPLATE = new URLTemplate("files/%s/copy"); 059 private static final URLTemplate ADD_COMMENT_URL_TEMPLATE = new URLTemplate("comments"); 060 private static final URLTemplate GET_COMMENTS_URL_TEMPLATE = new URLTemplate("files/%s/comments"); 061 private static final URLTemplate METADATA_URL_TEMPLATE = new URLTemplate("files/%s/metadata/%s/%s"); 062 private static final URLTemplate ADD_TASK_URL_TEMPLATE = new URLTemplate("tasks"); 063 private static final URLTemplate GET_TASKS_URL_TEMPLATE = new URLTemplate("files/%s/tasks"); 064 private static final URLTemplate GET_THUMBNAIL_PNG_TEMPLATE = new URLTemplate("files/%s/thumbnail.png"); 065 private static final URLTemplate GET_THUMBNAIL_JPG_TEMPLATE = new URLTemplate("files/%s/thumbnail.jpg"); 066 private static final int BUFFER_SIZE = 8192; 067 068 069 /** 070 * Constructs a BoxFile for a file with a given ID. 071 * @param api the API connection to be used by the file. 072 * @param id the ID of the file. 073 */ 074 public BoxFile(BoxAPIConnection api, String id) { 075 super(api, id); 076 } 077 078 /** 079 * {@inheritDoc} 080 */ 081 @Override 082 protected URL getItemURL() { 083 return FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 084 } 085 086 @Override 087 public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate, 088 BoxSharedLink.Permissions permissions) { 089 090 BoxSharedLink sharedLink = new BoxSharedLink(access, unshareDate, permissions); 091 Info info = new Info(); 092 info.setSharedLink(sharedLink); 093 094 this.updateInfo(info); 095 return info.getSharedLink(); 096 } 097 098 /** 099 * Adds new {@link BoxWebHook} to this {@link BoxFile}. 100 * 101 * @param address 102 * {@link BoxWebHook.Info#getAddress()} 103 * @param triggers 104 * {@link BoxWebHook.Info#getTriggers()} 105 * @return created {@link BoxWebHook.Info} 106 */ 107 public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) { 108 return BoxWebHook.create(this, address, triggers); 109 } 110 111 /** 112 * Adds a comment to this file. The message can contain @mentions by using the string @[userid:username] anywhere 113 * within the message, where userid and username are the ID and username of the person being mentioned. 114 * @see <a href="https://developers.box.com/docs/#comments-add-a-comment-to-an-item">the tagged_message field 115 * for including @mentions.</a> 116 * @param message the comment's message. 117 * @return information about the newly added comment. 118 */ 119 public BoxComment.Info addComment(String message) { 120 JsonObject itemJSON = new JsonObject(); 121 itemJSON.add("type", "file"); 122 itemJSON.add("id", this.getID()); 123 124 JsonObject requestJSON = new JsonObject(); 125 requestJSON.add("item", itemJSON); 126 if (BoxComment.messageContainsMention(message)) { 127 requestJSON.add("tagged_message", message); 128 } else { 129 requestJSON.add("message", message); 130 } 131 132 URL url = ADD_COMMENT_URL_TEMPLATE.build(this.getAPI().getBaseURL()); 133 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); 134 request.setBody(requestJSON.toString()); 135 BoxJSONResponse response = (BoxJSONResponse) request.send(); 136 JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); 137 138 BoxComment addedComment = new BoxComment(this.getAPI(), responseJSON.get("id").asString()); 139 return addedComment.new Info(responseJSON); 140 } 141 142 /** 143 * Adds a new task to this file. The task can have an optional message to include, and a due date. 144 * @param action the action the task assignee will be prompted to do. 145 * @param message an optional message to include with the task. 146 * @param dueAt the day at which this task is due. 147 * @return information about the newly added task. 148 */ 149 public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt) { 150 JsonObject itemJSON = new JsonObject(); 151 itemJSON.add("type", "file"); 152 itemJSON.add("id", this.getID()); 153 154 JsonObject requestJSON = new JsonObject(); 155 requestJSON.add("item", itemJSON); 156 requestJSON.add("action", action.toJSONString()); 157 158 if (message != null && !message.isEmpty()) { 159 requestJSON.add("message", message); 160 } 161 162 if (dueAt != null) { 163 requestJSON.add("due_at", BoxDateFormat.format(dueAt)); 164 } 165 166 URL url = ADD_TASK_URL_TEMPLATE.build(this.getAPI().getBaseURL()); 167 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); 168 request.setBody(requestJSON.toString()); 169 BoxJSONResponse response = (BoxJSONResponse) request.send(); 170 JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); 171 172 BoxTask addedTask = new BoxTask(this.getAPI(), responseJSON.get("id").asString()); 173 return addedTask.new Info(responseJSON); 174 } 175 176 /** 177 * Gets an expiring URL for downloading a file directly from Box. This can be user, 178 * for example, for sending as a redirect to a browser to cause the browser 179 * to download the file directly from Box. 180 * @return the temporary download URL 181 */ 182 public URL getDownloadURL() { 183 URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 184 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 185 request.setFollowRedirects(false); 186 187 BoxRedirectResponse response = (BoxRedirectResponse) request.send(); 188 189 return response.getRedirectURL(); 190 } 191 192 /** 193 * Downloads the contents of this file to a given OutputStream. 194 * @param output the stream to where the file will be written. 195 */ 196 public void download(OutputStream output) { 197 this.download(output, null); 198 } 199 200 /** 201 * Downloads the contents of this file to a given OutputStream while reporting the progress to a ProgressListener. 202 * @param output the stream to where the file will be written. 203 * @param listener a listener for monitoring the download's progress. 204 */ 205 public void download(OutputStream output, ProgressListener listener) { 206 URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 207 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 208 BoxAPIResponse response = request.send(); 209 InputStream input = response.getBody(listener); 210 211 byte[] buffer = new byte[BUFFER_SIZE]; 212 try { 213 int n = input.read(buffer); 214 while (n != -1) { 215 output.write(buffer, 0, n); 216 n = input.read(buffer); 217 } 218 } catch (IOException e) { 219 throw new BoxAPIException("Couldn't connect to the Box API due to a network error.", e); 220 } finally { 221 response.disconnect(); 222 } 223 } 224 225 /** 226 * Downloads a part of this file's contents, starting at specified byte offset. 227 * @param output the stream to where the file will be written. 228 * @param offset the byte offset at which to start the download. 229 */ 230 public void downloadRange(OutputStream output, long offset) { 231 this.downloadRange(output, offset, -1); 232 } 233 234 /** 235 * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd. 236 * @param output the stream to where the file will be written. 237 * @param rangeStart the byte offset at which to start the download. 238 * @param rangeEnd the byte offset at which to stop the download. 239 */ 240 public void downloadRange(OutputStream output, long rangeStart, long rangeEnd) { 241 this.downloadRange(output, rangeStart, rangeEnd, null); 242 } 243 244 /** 245 * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd, while reporting the 246 * progress to a ProgressListener. 247 * @param output the stream to where the file will be written. 248 * @param rangeStart the byte offset at which to start the download. 249 * @param rangeEnd the byte offset at which to stop the download. 250 * @param listener a listener for monitoring the download's progress. 251 */ 252 public void downloadRange(OutputStream output, long rangeStart, long rangeEnd, ProgressListener listener) { 253 URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 254 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 255 if (rangeEnd > 0) { 256 request.addHeader("Range", String.format("bytes=%s-%s", Long.toString(rangeStart), 257 Long.toString(rangeEnd))); 258 } else { 259 request.addHeader("Range", String.format("bytes=%s-", Long.toString(rangeStart))); 260 } 261 262 BoxAPIResponse response = request.send(); 263 InputStream input = response.getBody(listener); 264 265 byte[] buffer = new byte[BUFFER_SIZE]; 266 try { 267 int n = input.read(buffer); 268 while (n != -1) { 269 output.write(buffer, 0, n); 270 n = input.read(buffer); 271 } 272 } catch (IOException e) { 273 throw new BoxAPIException("Couldn't connect to the Box API due to a network error.", e); 274 } finally { 275 response.disconnect(); 276 } 277 } 278 279 @Override 280 public BoxFile.Info copy(BoxFolder destination) { 281 return this.copy(destination, null); 282 } 283 284 @Override 285 public BoxFile.Info copy(BoxFolder destination, String newName) { 286 URL url = COPY_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 287 288 JsonObject parent = new JsonObject(); 289 parent.add("id", destination.getID()); 290 291 JsonObject copyInfo = new JsonObject(); 292 copyInfo.add("parent", parent); 293 if (newName != null) { 294 copyInfo.add("name", newName); 295 } 296 297 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST"); 298 request.setBody(copyInfo.toString()); 299 BoxJSONResponse response = (BoxJSONResponse) request.send(); 300 JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); 301 BoxFile copiedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString()); 302 return copiedFile.new Info(responseJSON); 303 } 304 305 /** 306 * Deletes this file by moving it to the trash. 307 */ 308 public void delete() { 309 URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 310 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE"); 311 BoxAPIResponse response = request.send(); 312 response.disconnect(); 313 } 314 315 @Override 316 public BoxItem.Info move(BoxFolder destination) { 317 return this.move(destination, null); 318 } 319 320 @Override 321 public BoxItem.Info move(BoxFolder destination, String newName) { 322 URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 323 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); 324 325 JsonObject parent = new JsonObject(); 326 parent.add("id", destination.getID()); 327 328 JsonObject updateInfo = new JsonObject(); 329 updateInfo.add("parent", parent); 330 if (newName != null) { 331 updateInfo.add("name", newName); 332 } 333 334 request.setBody(updateInfo.toString()); 335 BoxJSONResponse response = (BoxJSONResponse) request.send(); 336 JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); 337 BoxFile movedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString()); 338 return movedFile.new Info(responseJSON); 339 } 340 341 /** 342 * Renames this file. 343 * @param newName the new name of the file. 344 */ 345 public void rename(String newName) { 346 URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 347 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); 348 349 JsonObject updateInfo = new JsonObject(); 350 updateInfo.add("name", newName); 351 352 request.setBody(updateInfo.toString()); 353 BoxAPIResponse response = request.send(); 354 response.disconnect(); 355 } 356 357 @Override 358 public BoxFile.Info getInfo() { 359 URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 360 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 361 BoxJSONResponse response = (BoxJSONResponse) request.send(); 362 return new Info(response.getJSON()); 363 } 364 365 @Override 366 public BoxFile.Info getInfo(String... fields) { 367 String queryString = new QueryStringBuilder().appendParam("fields", fields).toString(); 368 URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 369 370 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 371 BoxJSONResponse response = (BoxJSONResponse) request.send(); 372 return new Info(response.getJSON()); 373 } 374 375 /** 376 * Updates the information about this file with any info fields that have been modified locally. 377 * 378 * <p>The only fields that will be updated are the ones that have been modified locally. For example, the following 379 * code won't update any information (or even send a network request) since none of the info's fields were 380 * changed:</p> 381 * 382 * <pre>BoxFile file = new File(api, id); 383 *BoxFile.Info info = file.getInfo(); 384 *file.updateInfo(info);</pre> 385 * 386 * @param info the updated info. 387 */ 388 public void updateInfo(BoxFile.Info info) { 389 URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 390 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); 391 request.setBody(info.getPendingChanges()); 392 BoxJSONResponse response = (BoxJSONResponse) request.send(); 393 JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); 394 info.update(jsonObject); 395 } 396 397 /** 398 * Gets any previous versions of this file. Note that only users with premium accounts will be able to retrieve 399 * previous versions of their files. 400 * @return a list of previous file versions. 401 */ 402 public Collection<BoxFileVersion> getVersions() { 403 URL url = VERSIONS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 404 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 405 BoxJSONResponse response = (BoxJSONResponse) request.send(); 406 407 JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); 408 JsonArray entries = jsonObject.get("entries").asArray(); 409 Collection<BoxFileVersion> versions = new ArrayList<BoxFileVersion>(); 410 for (JsonValue entry : entries) { 411 versions.add(new BoxFileVersion(this.getAPI(), entry.asObject(), this.getID())); 412 } 413 414 return versions; 415 } 416 417 /** 418 * Checks if the file can be successfully uploaded by using the preflight check. 419 * @param name the name to give the uploaded file or null to use existing name. 420 * @param fileSize the size of the file used for account capacity calculations. 421 * @param parentID the ID of the parent folder that the new version is being uploaded to. 422 */ 423 public void canUploadVersion(String name, long fileSize, String parentID) { 424 URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 425 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS"); 426 427 JsonObject parent = new JsonObject(); 428 parent.add("id", parentID); 429 430 JsonObject preflightInfo = new JsonObject(); 431 preflightInfo.add("parent", parent); 432 if (name != null) { 433 preflightInfo.add("name", name); 434 } 435 436 preflightInfo.add("size", fileSize); 437 438 request.setBody(preflightInfo.toString()); 439 BoxAPIResponse response = request.send(); 440 response.disconnect(); 441 } 442 443 /** 444 * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts 445 * will be able to view and recover previous versions of the file. 446 * @param fileContent a stream containing the new file contents. 447 */ 448 public void uploadVersion(InputStream fileContent) { 449 this.uploadVersion(fileContent, null); 450 } 451 452 /** 453 * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts 454 * will be able to view and recover previous versions of the file. 455 * @param fileContent a stream containing the new file contents. 456 * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents. 457 * 458 */ 459 public void uploadVersion(InputStream fileContent, String fileContentSHA1) { 460 this.uploadVersion(fileContent, fileContentSHA1, null); 461 } 462 463 /** 464 * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts 465 * will be able to view and recover previous versions of the file. 466 * @param fileContent a stream containing the new file contents. 467 * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents. 468 * @param modified the date that the new version was modified. 469 */ 470 public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date modified) { 471 this.uploadVersion(fileContent, fileContentSHA1, modified, 0, null); 472 } 473 474 /** 475 * Uploads a new version of this file, replacing the current version, while reporting the progress to a 476 * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions 477 * of the file. 478 * @param fileContent a stream containing the new file contents. 479 * @param modified the date that the new version was modified. 480 * @param fileSize the size of the file used for determining the progress of the upload. 481 * @param listener a listener for monitoring the upload's progress. 482 */ 483 public void uploadVersion(InputStream fileContent, Date modified, long fileSize, ProgressListener listener) { 484 this.uploadVersion(fileContent, null, modified, fileSize, listener); 485 } 486 487 /** 488 * Uploads a new version of this file, replacing the current version, while reporting the progress to a 489 * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions 490 * of the file. 491 * @param fileContent a stream containing the new file contents. 492 * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header 493 * @param modified the date that the new version was modified. 494 * @param fileSize the size of the file used for determining the progress of the upload. 495 * @param listener a listener for monitoring the upload's progress. 496 */ 497 public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date modified, long fileSize, 498 ProgressListener listener) { 499 URL uploadURL = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID()); 500 BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL); 501 502 if (fileSize > 0) { 503 request.setFile(fileContent, "", fileSize); 504 } else { 505 request.setFile(fileContent, ""); 506 } 507 508 if (fileContentSHA1 != null) { 509 request.setContentSHA1(fileContentSHA1); 510 } 511 512 if (modified != null) { 513 request.putField("content_modified_at", modified); 514 } 515 516 BoxJSONResponse response; 517 if (listener == null) { 518 response = (BoxJSONResponse) request.send(); 519 } else { 520 response = (BoxJSONResponse) request.send(listener); 521 } 522 response.getJSON(); 523 } 524 525 /** 526 * Gets an expiring URL for creating an embedded preview session. The URL will expire after 60 seconds and the 527 * preview session will expire after 60 minutes. 528 * @return the expiring preview link 529 */ 530 public URL getPreviewLink() { 531 BoxFile.Info info = this.getInfo("expiring_embed_link"); 532 533 return info.getPreviewLink(); 534 } 535 536 537 /** 538 * Retrieves a thumbnail, or smaller image representation, of this file. Sizes of 32x32, 64x64, 128x128, 539 * and 256x256 can be returned in the .png format and sizes of 32x32, 94x94, 160x160, and 320x320 can be returned 540 * in the .jpg format. 541 * @param fileType either PNG of JPG 542 * @param minWidth minimum width 543 * @param minHeight minimum height 544 * @param maxWidth maximum width 545 * @param maxHeight maximum height 546 * @return the byte array of the thumbnail image 547 */ 548 public byte[] getThumbnail(ThumbnailFileType fileType, int minWidth, int minHeight, int maxWidth, int maxHeight) { 549 QueryStringBuilder builder = new QueryStringBuilder(); 550 builder.appendParam("min_width", minWidth); 551 builder.appendParam("min_height", minHeight); 552 builder.appendParam("max_width", maxWidth); 553 builder.appendParam("max_height", maxHeight); 554 555 URLTemplate template; 556 if (fileType == ThumbnailFileType.PNG) { 557 template = GET_THUMBNAIL_PNG_TEMPLATE; 558 } else if (fileType == ThumbnailFileType.JPG) { 559 template = GET_THUMBNAIL_JPG_TEMPLATE; 560 } else { 561 throw new BoxAPIException("Unsupported thumbnail file type"); 562 } 563 URL url = template.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID()); 564 565 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 566 BoxAPIResponse response = request.send(); 567 568 ByteArrayOutputStream thumbOut = new ByteArrayOutputStream(); 569 InputStream body = response.getBody(); 570 byte[] buffer = new byte[BUFFER_SIZE]; 571 try { 572 int n = body.read(buffer); 573 while (n != -1) { 574 thumbOut.write(buffer, 0, n); 575 n = body.read(buffer); 576 } 577 } catch (IOException e) { 578 throw new BoxAPIException("Error reading thumbnail bytes from response body", e); 579 } finally { 580 response.disconnect(); 581 } 582 583 return thumbOut.toByteArray(); 584 } 585 586 /** 587 * Gets a list of any comments on this file. 588 * @return a list of comments on this file. 589 */ 590 public List<BoxComment.Info> getComments() { 591 URL url = GET_COMMENTS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 592 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 593 BoxJSONResponse response = (BoxJSONResponse) request.send(); 594 JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); 595 596 int totalCount = responseJSON.get("total_count").asInt(); 597 List<BoxComment.Info> comments = new ArrayList<BoxComment.Info>(totalCount); 598 JsonArray entries = responseJSON.get("entries").asArray(); 599 for (JsonValue value : entries) { 600 JsonObject commentJSON = value.asObject(); 601 BoxComment comment = new BoxComment(this.getAPI(), commentJSON.get("id").asString()); 602 BoxComment.Info info = comment.new Info(commentJSON); 603 comments.add(info); 604 } 605 606 return comments; 607 } 608 609 /** 610 * Gets a list of any tasks on this file. 611 * @return a list of tasks on this file. 612 */ 613 public List<BoxTask.Info> getTasks() { 614 URL url = GET_TASKS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 615 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 616 BoxJSONResponse response = (BoxJSONResponse) request.send(); 617 JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); 618 619 int totalCount = responseJSON.get("total_count").asInt(); 620 List<BoxTask.Info> tasks = new ArrayList<BoxTask.Info>(totalCount); 621 JsonArray entries = responseJSON.get("entries").asArray(); 622 for (JsonValue value : entries) { 623 JsonObject taskJSON = value.asObject(); 624 BoxTask task = new BoxTask(this.getAPI(), taskJSON.get("id").asString()); 625 BoxTask.Info info = task.new Info(taskJSON); 626 tasks.add(info); 627 } 628 629 return tasks; 630 } 631 632 /** 633 * Creates metadata on this file in the global properties template. 634 * @param metadata The new metadata values. 635 * @return the metadata returned from the server. 636 */ 637 public Metadata createMetadata(Metadata metadata) { 638 return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata); 639 } 640 641 /** 642 * Creates metadata on this file in the specified template type. 643 * @param typeName the metadata template type name. 644 * @param metadata the new metadata values. 645 * @return the metadata returned from the server. 646 */ 647 public Metadata createMetadata(String typeName, Metadata metadata) { 648 String scope = Metadata.scopeBasedOnType(typeName); 649 return this.createMetadata(typeName, scope, metadata); 650 } 651 652 /** 653 * Creates metadata on this file in the specified template type. 654 * @param typeName the metadata template type name. 655 * @param scope the metadata scope (global or enterprise). 656 * @param metadata the new metadata values. 657 * @return the metadata returned from the server. 658 */ 659 public Metadata createMetadata(String typeName, String scope, Metadata metadata) { 660 URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, typeName); 661 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "POST"); 662 request.addHeader("Content-Type", "application/json"); 663 request.setBody(metadata.toString()); 664 BoxJSONResponse response = (BoxJSONResponse) request.send(); 665 return new Metadata(JsonObject.readFrom(response.getJSON())); 666 } 667 668 /** 669 * Locks a file. 670 * @param expiresAt expiration date of the lock. 671 * @return the lock returned from the server. 672 */ 673 public BoxLock lock(Date expiresAt) { 674 return this.lock(expiresAt, false); 675 } 676 677 /** 678 * Locks a file. 679 * @param expiresAt expiration date of the lock. 680 * @param isDownloadPrevented is downloading of file prevented when locked. 681 * @return the lock returned from the server. 682 */ 683 public BoxLock lock(Date expiresAt, boolean isDownloadPrevented) { 684 String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString(); 685 URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 686 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT"); 687 688 JsonObject lockConfig = new JsonObject(); 689 lockConfig.add("type", "lock"); 690 lockConfig.add("expires_at", BoxDateFormat.format(expiresAt)); 691 lockConfig.add("is_download_prevented", isDownloadPrevented); 692 693 JsonObject requestJSON = new JsonObject(); 694 requestJSON.add("lock", lockConfig); 695 request.setBody(requestJSON.toString()); 696 697 BoxJSONResponse response = (BoxJSONResponse) request.send(); 698 699 JsonObject responseJSON = JsonObject.readFrom(response.getJSON()); 700 JsonValue lockValue = responseJSON.get("lock"); 701 JsonObject lockJSON = JsonObject.readFrom(lockValue.toString()); 702 703 return new BoxLock(lockJSON, this.getAPI()); 704 } 705 706 /** 707 * Unlocks a file. 708 */ 709 public void unlock() { 710 String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString(); 711 URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 712 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT"); 713 714 JsonObject lockObject = new JsonObject(); 715 lockObject.add("lock", JsonObject.NULL); 716 717 request.setBody(lockObject.toString()); 718 request.send(); 719 } 720 721 /** 722 * Used to retrieve all metadata associated with the file. 723 * @param fields the optional fields to retrieve. 724 * @return An iterable of metadata instances associated with the file. 725 */ 726 public Iterable<Metadata> getAllMetadata(String ... fields) { 727 return Metadata.getAllMetadata(this, fields); 728 } 729 730 /** 731 * Gets the file properties metadata. 732 * @return the metadata returned from the server. 733 */ 734 public Metadata getMetadata() { 735 return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE); 736 } 737 738 /** 739 * Gets the file metadata of specified template type. 740 * @param typeName the metadata template type name. 741 * @return the metadata returned from the server. 742 */ 743 public Metadata getMetadata(String typeName) { 744 String scope = Metadata.scopeBasedOnType(typeName); 745 return this.getMetadata(typeName, scope); 746 } 747 748 /** 749 * Gets the file metadata of specified template type. 750 * @param typeName the metadata template type name. 751 * @param scope the metadata scope (global or enterprise). 752 * @return the metadata returned from the server. 753 */ 754 public Metadata getMetadata(String typeName, String scope) { 755 URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, typeName); 756 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET"); 757 BoxJSONResponse response = (BoxJSONResponse) request.send(); 758 return new Metadata(JsonObject.readFrom(response.getJSON())); 759 } 760 761 /** 762 * Updates the file metadata. 763 * @param metadata the new metadata values. 764 * @return the metadata returned from the server. 765 */ 766 public Metadata updateMetadata(Metadata metadata) { 767 String scope; 768 if (metadata.getScope().equals(Metadata.GLOBAL_METADATA_SCOPE)) { 769 scope = Metadata.GLOBAL_METADATA_SCOPE; 770 } else { 771 scope = Metadata.ENTERPRISE_METADATA_SCOPE; 772 } 773 774 URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), 775 scope, metadata.getTemplateName()); 776 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT"); 777 request.addHeader("Content-Type", "application/json-patch+json"); 778 request.setBody(metadata.getPatch()); 779 BoxJSONResponse response = (BoxJSONResponse) request.send(); 780 return new Metadata(JsonObject.readFrom(response.getJSON())); 781 } 782 783 /** 784 * Deletes the file properties metadata. 785 */ 786 public void deleteMetadata() { 787 this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE); 788 } 789 790 /** 791 * Deletes the file metadata of specified template type. 792 * @param typeName the metadata template type name. 793 */ 794 public void deleteMetadata(String typeName) { 795 String scope = Metadata.scopeBasedOnType(typeName); 796 this.deleteMetadata(typeName, scope); 797 } 798 799 /** 800 * Deletes the file metadata of specified template type. 801 * @param typeName the metadata template type name. 802 * @param scope the metadata scope (global or enterprise). 803 */ 804 public void deleteMetadata(String typeName, String scope) { 805 URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, typeName); 806 BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE"); 807 request.send(); 808 } 809 810 /** 811 * Used to retrieve the watermark for the file. 812 * If the file does not have a watermark applied to it, a 404 Not Found will be returned by API. 813 * @param fields the fields to retrieve. 814 * @return the watermark associated with the file. 815 */ 816 public BoxWatermark getWatermark(String... fields) { 817 return this.getWatermark(FILE_URL_TEMPLATE, fields); 818 } 819 820 /** 821 * Used to apply or update the watermark for the file. 822 * @return the watermark associated with the file. 823 */ 824 public BoxWatermark applyWatermark() { 825 return this.applyWatermark(FILE_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT); 826 } 827 828 /** 829 * Removes a watermark from the file. 830 * If the file did not have a watermark applied to it, a 404 Not Found will be returned by API. 831 */ 832 public void removeWatermark() { 833 this.removeWatermark(FILE_URL_TEMPLATE); 834 } 835 836 /** 837 * {@inheritDoc} 838 */ 839 @Override 840 public BoxFile.Info setCollections(BoxCollection... collections) { 841 JsonArray jsonArray = new JsonArray(); 842 for (BoxCollection collection : collections) { 843 JsonObject collectionJSON = new JsonObject(); 844 collectionJSON.add("id", collection.getID()); 845 jsonArray.add(collectionJSON); 846 } 847 JsonObject infoJSON = new JsonObject(); 848 infoJSON.add("collections", jsonArray); 849 850 String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString(); 851 URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 852 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT"); 853 request.setBody(infoJSON.toString()); 854 BoxJSONResponse response = (BoxJSONResponse) request.send(); 855 JsonObject jsonObject = JsonObject.readFrom(response.getJSON()); 856 return new Info(jsonObject); 857 } 858 859 /** 860 * Contains information about a BoxFile. 861 */ 862 public class Info extends BoxItem.Info { 863 private String sha1; 864 private String versionNumber; 865 private long commentCount; 866 private EnumSet<Permission> permissions; 867 private String extension; 868 private boolean isPackage; 869 private BoxFileVersion version; 870 private URL previewLink; 871 private BoxLock lock; 872 private boolean isWatermarked; 873 874 /** 875 * Constructs an empty Info object. 876 */ 877 public Info() { 878 super(); 879 } 880 881 /** 882 * Constructs an Info object by parsing information from a JSON string. 883 * @param json the JSON string to parse. 884 */ 885 public Info(String json) { 886 super(json); 887 } 888 889 /** 890 * Constructs an Info object using an already parsed JSON object. 891 * @param jsonObject the parsed JSON object. 892 */ 893 Info(JsonObject jsonObject) { 894 super(jsonObject); 895 } 896 897 @Override 898 public BoxFile getResource() { 899 return BoxFile.this; 900 } 901 902 /** 903 * Gets the SHA1 hash of the file. 904 * @return the SHA1 hash of the file. 905 */ 906 public String getSha1() { 907 return this.sha1; 908 } 909 910 /** 911 * Gets the lock of the file. 912 * @return the lock of the file. 913 */ 914 public BoxLock getLock() { 915 return this.lock; 916 } 917 918 /** 919 * Gets the current version number of the file. 920 * @return the current version number of the file. 921 */ 922 public String getVersionNumber() { 923 return this.versionNumber; 924 } 925 926 /** 927 * Gets the number of comments on the file. 928 * @return the number of comments on the file. 929 */ 930 public long getCommentCount() { 931 return this.commentCount; 932 } 933 934 /** 935 * Gets the permissions that the current user has on the file. 936 * @return the permissions that the current user has on the file. 937 */ 938 public EnumSet<Permission> getPermissions() { 939 return this.permissions; 940 } 941 942 /** 943 * Gets the extension suffix of the file, excluding the dot. 944 * @return the extension of the file. 945 */ 946 public String getExtension() { 947 return this.extension; 948 } 949 950 /** 951 * Gets whether or not the file is an OSX package. 952 * @return true if the file is an OSX package; otherwise false. 953 */ 954 public boolean getIsPackage() { 955 return this.isPackage; 956 } 957 958 /** 959 * Gets the current version details of the file. 960 * @return the current version details of the file. 961 */ 962 public BoxFileVersion getVersion() { 963 return this.version; 964 } 965 966 /** 967 * Gets the current expiring preview link. 968 * @return the expiring preview link 969 */ 970 public URL getPreviewLink() { 971 return this.previewLink; 972 } 973 974 /** 975 * Gets flag indicating whether this file is Watermarked. 976 * @return whether the file is watermarked or not 977 */ 978 public boolean getIsWatermarked() { 979 return this.isWatermarked; 980 } 981 982 @Override 983 protected void parseJSONMember(JsonObject.Member member) { 984 super.parseJSONMember(member); 985 986 String memberName = member.getName(); 987 JsonValue value = member.getValue(); 988 if (memberName.equals("sha1")) { 989 this.sha1 = value.asString(); 990 } else if (memberName.equals("version_number")) { 991 this.versionNumber = value.asString(); 992 } else if (memberName.equals("comment_count")) { 993 this.commentCount = value.asLong(); 994 } else if (memberName.equals("permissions")) { 995 this.permissions = this.parsePermissions(value.asObject()); 996 } else if (memberName.equals("extension")) { 997 this.extension = value.asString(); 998 } else if (memberName.equals("is_package")) { 999 this.isPackage = value.asBoolean(); 1000 } else if (memberName.equals("file_version")) { 1001 this.version = this.parseFileVersion(value.asObject()); 1002 } else if (memberName.equals("expiring_embed_link")) { 1003 try { 1004 String urlString = member.getValue().asObject().get("url").asString(); 1005 this.previewLink = new URL(urlString); 1006 } catch (MalformedURLException e) { 1007 throw new BoxAPIException("Couldn't parse expiring_embed_link/url for file", e); 1008 } 1009 } else if (memberName.equals("lock")) { 1010 if (value.isNull()) { 1011 this.lock = null; 1012 } else { 1013 this.lock = new BoxLock(value.asObject(), BoxFile.this.getAPI()); 1014 } 1015 } else if (memberName.equals("watermark_info")) { 1016 JsonObject jsonObject = value.asObject(); 1017 this.isWatermarked = jsonObject.get("is_watermarked").asBoolean(); 1018 } 1019 } 1020 1021 private EnumSet<Permission> parsePermissions(JsonObject jsonObject) { 1022 EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class); 1023 for (JsonObject.Member member : jsonObject) { 1024 JsonValue value = member.getValue(); 1025 if (value.isNull() || !value.asBoolean()) { 1026 continue; 1027 } 1028 1029 String memberName = member.getName(); 1030 if (memberName.equals("can_download")) { 1031 permissions.add(Permission.CAN_DOWNLOAD); 1032 } else if (memberName.equals("can_upload")) { 1033 permissions.add(Permission.CAN_UPLOAD); 1034 } else if (memberName.equals("can_rename")) { 1035 permissions.add(Permission.CAN_RENAME); 1036 } else if (memberName.equals("can_delete")) { 1037 permissions.add(Permission.CAN_DELETE); 1038 } else if (memberName.equals("can_share")) { 1039 permissions.add(Permission.CAN_SHARE); 1040 } else if (memberName.equals("can_set_share_access")) { 1041 permissions.add(Permission.CAN_SET_SHARE_ACCESS); 1042 } else if (memberName.equals("can_preview")) { 1043 permissions.add(Permission.CAN_PREVIEW); 1044 } else if (memberName.equals("can_comment")) { 1045 permissions.add(Permission.CAN_COMMENT); 1046 } 1047 } 1048 1049 return permissions; 1050 } 1051 1052 private BoxFileVersion parseFileVersion(JsonObject jsonObject) { 1053 return new BoxFileVersion(BoxFile.this.getAPI(), jsonObject, BoxFile.this.getID()); 1054 } 1055 } 1056 1057 /** 1058 * Enumerates the possible permissions that a user can have on a file. 1059 */ 1060 public enum Permission { 1061 /** 1062 * The user can download the file. 1063 */ 1064 CAN_DOWNLOAD ("can_download"), 1065 1066 /** 1067 * The user can upload new versions of the file. 1068 */ 1069 CAN_UPLOAD ("can_upload"), 1070 1071 /** 1072 * The user can rename the file. 1073 */ 1074 CAN_RENAME ("can_rename"), 1075 1076 /** 1077 * The user can delete the file. 1078 */ 1079 CAN_DELETE ("can_delete"), 1080 1081 /** 1082 * The user can share the file. 1083 */ 1084 CAN_SHARE ("can_share"), 1085 1086 /** 1087 * The user can set the access level for shared links to the file. 1088 */ 1089 CAN_SET_SHARE_ACCESS ("can_set_share_access"), 1090 1091 /** 1092 * The user can preview the file. 1093 */ 1094 CAN_PREVIEW ("can_preview"), 1095 1096 /** 1097 * The user can comment on the file. 1098 */ 1099 CAN_COMMENT ("can_comment"); 1100 1101 private final String jsonValue; 1102 1103 private Permission(String jsonValue) { 1104 this.jsonValue = jsonValue; 1105 } 1106 1107 static Permission fromJSONValue(String jsonValue) { 1108 return Permission.valueOf(jsonValue.toUpperCase()); 1109 } 1110 1111 String toJSONValue() { 1112 return this.jsonValue; 1113 } 1114 } 1115}