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