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