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