001package com.box.sdk;
002
003import static com.box.sdk.BinaryBodyUtils.writeStream;
004import static com.box.sdk.BinaryBodyUtils.writeStreamWithContentLength;
005import static com.box.sdk.http.ContentType.APPLICATION_JSON;
006import static com.box.sdk.http.ContentType.APPLICATION_JSON_PATCH;
007import static com.eclipsesource.json.Json.NULL;
008
009import com.box.sdk.http.HttpMethod;
010import com.box.sdk.internal.utils.Parsers;
011import com.box.sdk.sharedlink.BoxSharedLinkRequest;
012import com.eclipsesource.json.Json;
013import com.eclipsesource.json.JsonArray;
014import com.eclipsesource.json.JsonObject;
015import com.eclipsesource.json.JsonValue;
016import java.io.IOException;
017import java.io.InputStream;
018import java.io.OutputStream;
019import java.net.MalformedURLException;
020import java.net.URL;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collection;
024import java.util.Date;
025import java.util.EnumSet;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Optional;
030import java.util.Set;
031import java.util.concurrent.TimeUnit;
032
033
034/**
035 * Represents an individual file on Box. This class can be used to download a file's contents, upload new versions, and
036 * perform other common file operations (move, copy, delete, etc.).
037 *
038 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked
039 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error
040 * handling for errors related to the Box REST API, you should capture this exception explicitly.
041 */
042@BoxResourceType("file")
043public class BoxFile extends BoxItem {
044
045    /**
046     * An array of all possible file fields that can be requested when calling {@link #getInfo(String...)}.
047     */
048    public static final String[] ALL_FIELDS = {"type", "id", "sequence_id", "etag", "sha1", "name",
049        "description", "size", "path_collection", "created_at", "modified_at",
050        "trashed_at", "purged_at", "content_created_at", "content_modified_at",
051        "created_by", "modified_by", "owned_by", "shared_link", "parent",
052        "item_status", "version_number", "comment_count", "permissions", "tags",
053        "lock", "extension", "is_package", "file_version", "collections",
054        "watermark_info", "metadata", "representations",
055        "is_external_only", "expiring_embed_link", "allowed_invitee_roles",
056        "has_collaborations", "disposition_at", "is_accessible_via_shared_link"};
057
058    /**
059     * An array of all possible version fields that can be requested when calling {@link #getVersions(String...)}.
060     */
061    public static final String[] ALL_VERSION_FIELDS = {"id", "sha1", "name", "size", "uploader_display_name",
062        "created_at", "modified_at", "modified_by", "trashed_at", "trashed_by", "restored_at", "restored_by",
063        "purged_at", "file_version", "version_number"};
064    /**
065     * File URL Template.
066     */
067    public static final URLTemplate FILE_URL_TEMPLATE = new URLTemplate("files/%s");
068    /**
069     * Content URL Template.
070     */
071    public static final URLTemplate CONTENT_URL_TEMPLATE = new URLTemplate("files/%s/content");
072    /**
073     * Versions URL Template.
074     */
075    public static final URLTemplate VERSIONS_URL_TEMPLATE = new URLTemplate("files/%s/versions");
076    /**
077     * Copy URL Template.
078     */
079    public static final URLTemplate COPY_URL_TEMPLATE = new URLTemplate("files/%s/copy");
080    /**
081     * Add Comment URL Template.
082     */
083    public static final URLTemplate ADD_COMMENT_URL_TEMPLATE = new URLTemplate("comments");
084    /**
085     * Get Comments URL Template.
086     */
087    public static final URLTemplate GET_COMMENTS_URL_TEMPLATE = new URLTemplate("files/%s/comments");
088    /**
089     * Metadata URL Template.
090     */
091    public static final URLTemplate METADATA_URL_TEMPLATE = new URLTemplate("files/%s/metadata/%s/%s");
092    /**
093     * Add Task URL Template.
094     */
095    public static final URLTemplate ADD_TASK_URL_TEMPLATE = new URLTemplate("tasks");
096    /**
097     * Get Tasks URL Template.
098     */
099    public static final URLTemplate GET_TASKS_URL_TEMPLATE = new URLTemplate("files/%s/tasks");
100    /**
101     * Get Thumbnail PNG Template.
102     */
103    public static final URLTemplate GET_THUMBNAIL_PNG_TEMPLATE = new URLTemplate("files/%s/thumbnail.png");
104    /**
105     * Get Thumbnail JPG Template.
106     */
107    public static final URLTemplate GET_THUMBNAIL_JPG_TEMPLATE = new URLTemplate("files/%s/thumbnail.jpg");
108    /**
109     * Upload Session URL Template.
110     */
111    public static final URLTemplate UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/%s/upload_sessions");
112    /**
113     * Upload Session Status URL Template.
114     */
115    public static final URLTemplate UPLOAD_SESSION_STATUS_URL_TEMPLATE = new URLTemplate(
116        "files/upload_sessions/%s/status");
117    /**
118     * Abort Upload Session URL Template.
119     */
120    public static final URLTemplate ABORT_UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/upload_sessions/%s");
121    /**
122     * Add Collaborations URL Template.
123     */
124    public static final URLTemplate ADD_COLLABORATION_URL = new URLTemplate("collaborations");
125    /**
126     * Get All File Collaborations URL Template.
127     */
128    public static final URLTemplate GET_ALL_FILE_COLLABORATIONS_URL = new URLTemplate("files/%s/collaborations");
129    /**
130     * Describes file item type.
131     */
132    static final String TYPE = "file";
133    private static final int GET_COLLABORATORS_PAGE_SIZE = 1000;
134
135    /**
136     * Constructs a BoxFile for a file with a given ID.
137     *
138     * @param api the API connection to be used by the file.
139     * @param id  the ID of the file.
140     */
141    public BoxFile(BoxAPIConnection api, String id) {
142        super(api, id);
143    }
144
145    /**
146     * {@inheritDoc}
147     */
148    @Override
149    protected URL getItemURL() {
150        return FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
151    }
152
153    /**
154     * Creates a shared link.
155     *
156     * @param sharedLinkRequest Shared link to create
157     * @return Created shared link.
158     */
159    public BoxSharedLink createSharedLink(BoxSharedLinkRequest sharedLinkRequest) {
160        return createSharedLink(sharedLinkRequest.asSharedLink());
161    }
162
163    private BoxSharedLink createSharedLink(BoxSharedLink sharedLink) {
164        Info info = new Info();
165        info.setSharedLink(sharedLink);
166
167        this.updateInfo(info);
168        return info.getSharedLink();
169    }
170
171    /**
172     * Adds new {@link BoxWebHook} to this {@link BoxFile}.
173     *
174     * @param address  {@link BoxWebHook.Info#getAddress()}
175     * @param triggers {@link BoxWebHook.Info#getTriggers()}
176     * @return created {@link BoxWebHook.Info}
177     */
178    public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) {
179        return BoxWebHook.create(this, address, triggers);
180    }
181
182    /**
183     * Adds a comment to this file. The message can contain @mentions by using the string @[userid:username] anywhere
184     * within the message, where userid and username are the ID and username of the person being mentioned.
185     *
186     * @param message the comment's message.
187     * @return information about the newly added comment.
188     * @see <a href="https://developers.box.com/docs/#comments-add-a-comment-to-an-item">the tagged_message field
189     * for including @mentions.</a>
190     */
191    public BoxComment.Info addComment(String message) {
192        JsonObject itemJSON = new JsonObject();
193        itemJSON.add("type", "file");
194        itemJSON.add("id", this.getID());
195
196        JsonObject requestJSON = new JsonObject();
197        requestJSON.add("item", itemJSON);
198        if (BoxComment.messageContainsMention(message)) {
199            requestJSON.add("tagged_message", message);
200        } else {
201            requestJSON.add("message", message);
202        }
203
204        URL url = ADD_COMMENT_URL_TEMPLATE.build(this.getAPI().getBaseURL());
205        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
206        request.setBody(requestJSON.toString());
207        try (BoxJSONResponse response = request.send()) {
208            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
209
210            BoxComment addedComment = new BoxComment(this.getAPI(), responseJSON.get("id").asString());
211            return addedComment.new Info(responseJSON);
212        }
213    }
214
215    /**
216     * Adds a new task to this file. The task can have an optional message to include, and a due date.
217     *
218     * @param action  the action the task assignee will be prompted to do.
219     * @param message an optional message to include with the task.
220     * @param dueAt   the day at which this task is due.
221     * @return information about the newly added task.
222     */
223    public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt) {
224        return this.addTask(action, message, dueAt, null);
225    }
226
227    /**
228     * Adds a new task to this file. The task can have an optional message to include, due date,
229     * and task completion rule.
230     *
231     * @param action         the action the task assignee will be prompted to do.
232     * @param message        an optional message to include with the task.
233     * @param dueAt          the day at which this task is due.
234     * @param completionRule the rule for completing the task.
235     * @return information about the newly added task.
236     */
237    public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt,
238                                BoxTask.CompletionRule completionRule) {
239        JsonObject itemJSON = new JsonObject();
240        itemJSON.add("type", "file");
241        itemJSON.add("id", this.getID());
242
243        JsonObject requestJSON = new JsonObject();
244        requestJSON.add("item", itemJSON);
245        requestJSON.add("action", action.toJSONString());
246
247        if (message != null && !message.isEmpty()) {
248            requestJSON.add("message", message);
249        }
250
251        if (dueAt != null) {
252            requestJSON.add("due_at", BoxDateFormat.format(dueAt));
253        }
254
255        if (completionRule != null) {
256            requestJSON.add("completion_rule", completionRule.toJSONString());
257        }
258
259        URL url = ADD_TASK_URL_TEMPLATE.build(this.getAPI().getBaseURL());
260        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
261        request.setBody(requestJSON.toString());
262        try (BoxJSONResponse response = request.send()) {
263            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
264
265            BoxTask addedTask = new BoxTask(this.getAPI(), responseJSON.get("id").asString());
266            return addedTask.new Info(responseJSON);
267        }
268    }
269
270    /**
271     * Gets an expiring URL for downloading a file directly from Box. This can be user,
272     * for example, for sending as a redirect to a browser to cause the browser
273     * to download the file directly from Box.
274     *
275     * @return the temporary download URL
276     */
277    public URL getDownloadURL() {
278        URL url = getDownloadUrl();
279        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
280        request.setFollowRedirects(false);
281
282        try (BoxAPIResponse response = request.send()) {
283            String location = response.getHeaderField("location");
284
285            try {
286                return new URL(location);
287            } catch (MalformedURLException e) {
288                throw new RuntimeException(e);
289            }
290        }
291    }
292
293    /**
294     * Downloads the contents of this file to a given OutputStream.
295     *
296     * @param output the stream to where the file will be written.
297     */
298    public void download(OutputStream output) {
299        this.download(output, null);
300    }
301
302    /**
303     * Downloads the contents of this file to a given OutputStream while reporting the progress to a ProgressListener.
304     *
305     * @param output   the stream to where the file will be written.
306     * @param listener a listener for monitoring the download's progress.
307     */
308    public void download(OutputStream output, ProgressListener listener) {
309        URL url = getDownloadUrl();
310        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
311        BoxAPIResponse response = request.send();
312        writeStream(response, output, listener);
313    }
314
315    /**
316     * Downloads the content of the file to a given OutputStream using the provided shared link.
317     * @param api the API connection to be used to get download URL of the file.
318     * @param output the stream to where the file will be written.
319     * @param sharedLink the shared link of the file.
320     */
321    public static void downloadFromSharedLink(BoxAPIConnection api, OutputStream output, String sharedLink) {
322        downloadFromSharedLink(api, output, sharedLink, null, null);
323    }
324
325    /**
326     * Downloads the content of the file to a given OutputStream using the provided shared link.
327     * @param api the API connection to be used to get download URL of the file.
328     * @param output the stream to where the file will be written.
329     * @param sharedLink the shared link of the file.
330     * @param password the password for the shared link.
331     */
332    public static void downloadFromSharedLink(
333            BoxAPIConnection api, OutputStream output, String sharedLink, String password
334    ) {
335        downloadFromSharedLink(api, output, sharedLink, password, null);
336    }
337
338    /**
339     * Downloads the content of the file to a given OutputStream using the provided shared link.
340     * @param api the API connection to be used to get download URL of the file.
341     * @param output the stream to where the file will be written.
342     * @param sharedLink the shared link of the file.
343     * @param listener a listener for monitoring the download's progress.
344     */
345    public static void downloadFromSharedLink(
346            BoxAPIConnection api, OutputStream output, String sharedLink, ProgressListener listener
347    ) {
348        downloadFromSharedLink(api, output, sharedLink, null, listener);
349    }
350
351    /**
352     * Downloads the content of the file to a given OutputStream using the provided shared link.
353     * @param api the API connection to be used to get download URL of the file.
354     * @param output the stream to where the file will be written.
355     * @param sharedLink the shared link of the file.
356     * @param password the password for the shared link.
357     * @param listener a listener for monitoring the download's progress.
358     */
359    public static void downloadFromSharedLink(
360            BoxAPIConnection api, OutputStream output, String sharedLink, String password, ProgressListener listener
361    ) {
362        BoxItem.Info item = BoxItem.getSharedItem(api, sharedLink, password, "id");
363        if (!(item instanceof BoxFile.Info)) {
364            throw new BoxAPIException("The shared link provided is not a shared link for a file.");
365        }
366        BoxFile sharedFile = new BoxFile(api, item.getID());
367        URL url = sharedFile.getDownloadUrl();
368        BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
369        request.addHeader("BoxApi", BoxSharedLink.getSharedLinkHeaderValue(sharedLink, password));
370        BoxAPIResponse response = request.send();
371        writeStream(response, output, listener);
372    }
373
374    /**
375     * Downloads a part of this file's contents, starting at specified byte offset.
376     *
377     * @param output the stream to where the file will be written.
378     * @param offset the byte offset at which to start the download.
379     */
380    public void downloadRange(OutputStream output, long offset) {
381        this.downloadRange(output, offset, -1);
382    }
383
384    /**
385     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd.
386     *
387     * @param output     the stream to where the file will be written.
388     * @param rangeStart the byte offset at which to start the download.
389     * @param rangeEnd   the byte offset at which to stop the download.
390     */
391    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd) {
392        this.downloadRange(output, rangeStart, rangeEnd, null);
393    }
394
395    /**
396     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd, while reporting the
397     * progress to a ProgressListener.
398     *
399     * @param output     the stream to where the file will be written.
400     * @param rangeStart the byte offset at which to start the download.
401     * @param rangeEnd   the byte offset at which to stop the download.
402     * @param listener   a listener for monitoring the download's progress.
403     */
404    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd, ProgressListener listener) {
405        URL url = getDownloadUrl();
406        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
407        if (rangeEnd > 0) {
408            request.addHeader("Range", String.format("bytes=%s-%s", rangeStart, rangeEnd));
409        } else {
410            request.addHeader("Range", String.format("bytes=%s-", rangeStart));
411        }
412        writeStream(request.send(), output, listener);
413    }
414
415    /**
416     * Can be used to override the URL used for file download.
417     *
418     * @return URL for file downalod
419     */
420    protected URL getDownloadUrl() {
421        return CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
422    }
423
424    @Override
425    public BoxFile.Info copy(BoxFolder destination) {
426        return this.copy(destination, null);
427    }
428
429    @Override
430    public BoxFile.Info copy(BoxFolder destination, String newName) {
431        URL url = COPY_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
432
433        JsonObject parent = new JsonObject();
434        parent.add("id", destination.getID());
435
436        JsonObject copyInfo = new JsonObject();
437        copyInfo.add("parent", parent);
438        if (newName != null) {
439            copyInfo.add("name", newName);
440        }
441
442        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
443        request.setBody(copyInfo.toString());
444        try (BoxJSONResponse response = request.send()) {
445            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
446            BoxFile copiedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
447            return copiedFile.new Info(responseJSON);
448        }
449    }
450
451    /**
452     * Deletes this file by moving it to the trash.
453     */
454    public void delete() {
455        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
456        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
457        request.send().close();
458    }
459
460    @Override
461    public BoxItem.Info move(BoxFolder destination) {
462        return this.move(destination, null);
463    }
464
465    @Override
466    public BoxItem.Info move(BoxFolder destination, String newName) {
467        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
468        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
469
470        JsonObject parent = new JsonObject();
471        parent.add("id", destination.getID());
472
473        JsonObject updateInfo = new JsonObject();
474        updateInfo.add("parent", parent);
475        if (newName != null) {
476            updateInfo.add("name", newName);
477        }
478
479        request.setBody(updateInfo.toString());
480        try (BoxJSONResponse response = request.send()) {
481            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
482            BoxFile movedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
483            return movedFile.new Info(responseJSON);
484        }
485    }
486
487    /**
488     * Renames this file.
489     *
490     * @param newName the new name of the file.
491     */
492    public void rename(String newName) {
493        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
494        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
495
496        JsonObject updateInfo = new JsonObject();
497        updateInfo.add("name", newName);
498
499        request.setBody(updateInfo.toString());
500        try (BoxJSONResponse response = request.send()) {
501            response.getJSON();
502        }
503    }
504
505    @Override
506    public BoxFile.Info getInfo(String... fields) {
507        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
508        if (fields.length > 0) {
509            String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
510            url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
511        }
512
513        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
514        try (BoxJSONResponse response = request.send()) {
515            return new Info(response.getJSON());
516        }
517    }
518
519    /**
520     * Gets information about this item including a specified set of representations.
521     *
522     * @param representationHints hints for representations to be retrieved
523     * @param fields              the fields to retrieve.
524     * @return info about this item containing only the specified fields, including representations.
525     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
526     */
527    public BoxFile.Info getInfoWithRepresentations(String representationHints, String... fields) {
528        if (representationHints.matches(Representation.X_REP_HINTS_PATTERN)) {
529            //Since the user intends to get representations, add it to fields, even if user has missed it
530            Set<String> fieldsSet = new HashSet<>(Arrays.asList(fields));
531            fieldsSet.add("representations");
532            String queryString = new QueryStringBuilder().appendParam("fields",
533                fieldsSet.toArray(new String[0])).toString();
534            URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
535
536            BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
537            request.addHeader("X-Rep-Hints", representationHints);
538            try (BoxJSONResponse response = request.send()) {
539                return new Info(response.getJSON());
540            }
541        } else {
542            throw new BoxAPIException(
543                "Represention hints is not valid. Refer documention on how to construct X-Rep-Hints Header"
544            );
545        }
546    }
547
548    /**
549     * Fetches the contents of a file representation and writes them to the provided output stream.
550     *
551     * @param representationHint the X-Rep-Hints query for the representation to fetch.
552     * @param output             the output stream to write the contents to.
553     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
554     */
555    public void getRepresentationContent(String representationHint, OutputStream output) {
556
557        this.getRepresentationContent(representationHint, "", output);
558    }
559
560    /**
561     * Fetches the contents of a file representation with asset path and writes them to the provided output stream.
562     *
563     * @param representationHint the X-Rep-Hints query for the representation to fetch.
564     * @param assetPath          the path of the asset for representations containing multiple files.
565     * @param output             the output stream to write the contents to.
566     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
567     */
568    public void getRepresentationContent(String representationHint, String assetPath, OutputStream output) {
569        this.getRepresentationContent(representationHint, assetPath, output, Integer.MAX_VALUE);
570    }
571
572    /**
573     * Fetches the contents of a file representation with asset path and writes them to the provided output stream.
574     *
575     * @param representationHint the X-Rep-Hints query for the representation to fetch.
576     * @param assetPath          the path of the asset for representations containing multiple files.
577     * @param output             the output stream to write the contents to.
578     * @param maxRetries         the maximum number of attempts to call the request for retrieving status information
579     *                           indicating whether the representation has been generated and is ready to fetch.
580     *                           If the number of attempts is exceeded, the method will throw a BoxApiException.
581     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
582     */
583    public void getRepresentationContent(
584        String representationHint, String assetPath, OutputStream output, int maxRetries
585    ) {
586        List<Representation> reps = this.getInfoWithRepresentations(representationHint).getRepresentations();
587        if (reps.size() < 1) {
588            throw new BoxAPIException("No matching representations found for requested '" + representationHint
589                + "' hint");
590        }
591        Representation representation = reps.get(0);
592        String repState = representation.getStatus().getState();
593
594        switch (repState) {
595            case "viewable":
596            case "success":
597                this.makeRepresentationContentRequest(representation.getContent().getUrlTemplate(), assetPath, output);
598                break;
599            case "pending":
600            case "none":
601
602                String repContentURLString = null;
603                int attemptNumber = 0;
604                while (repContentURLString == null && attemptNumber < maxRetries) {
605                    repContentURLString = this.pollRepInfo(representation.getInfo().getUrl());
606                    try {
607                        Thread.sleep(100);
608                    } catch (InterruptedException e) {
609                        throw new RuntimeException(e);
610                    }
611                    attemptNumber++;
612                }
613
614                if (repContentURLString != null) {
615                    this.makeRepresentationContentRequest(repContentURLString, assetPath, output);
616                } else {
617                    throw new BoxAPIException(
618                        "Representation did not have a success status allowing it to be retrieved after "
619                            + maxRetries
620                            + " attempts"
621                    );
622                }
623
624                break;
625            case "error":
626                throw new BoxAPIException("Representation had error status");
627            default:
628                throw new BoxAPIException("Representation had unknown status");
629        }
630
631    }
632
633    private String pollRepInfo(URL infoURL) {
634
635        BoxJSONRequest infoRequest = new BoxJSONRequest(this.getAPI(), infoURL, HttpMethod.GET);
636        try (BoxJSONResponse infoResponse = infoRequest.send()) {
637            JsonObject response = infoResponse.getJsonObject();
638
639            Representation rep = new Representation(response);
640
641            String repState = rep.getStatus().getState();
642
643            switch (repState) {
644                case "viewable":
645                case "success":
646                    return rep.getContent().getUrlTemplate();
647                case "pending":
648                case "none":
649                    return null;
650                case "error":
651                    throw new BoxAPIException("Representation had error status");
652                default:
653                    throw new BoxAPIException("Representation had unknown status");
654            }
655        }
656    }
657
658    private void makeRepresentationContentRequest(
659        String representationURLTemplate, String assetPath, OutputStream output
660    ) {
661        try {
662            URL repURL = new URL(representationURLTemplate.replace("{+asset_path}", assetPath));
663            BoxAPIRequest repContentReq = new BoxAPIRequest(this.getAPI(), repURL, HttpMethod.GET);
664            BoxAPIResponse response = repContentReq.send();
665            writeStreamWithContentLength(response, output);
666        } catch (MalformedURLException ex) {
667
668            throw new BoxAPIException("Could not generate representation content URL");
669        }
670    }
671
672    /**
673     * Updates the information about this file with any info fields that have been modified locally.
674     *
675     * <p>The only fields that will be updated are the ones that have been modified locally. For example, the following
676     * code won't update any information (or even send a network request) since none of the info's fields were
677     * changed:</p>
678     *
679     * <pre>BoxFile file = new File(api, id);
680     * BoxFile.Info info = file.getInfo();
681     * file.updateInfo(info);</pre>
682     *
683     * @param info the updated info.
684     */
685    public void updateInfo(BoxFile.Info info) {
686        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
687        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
688        request.setBody(info.getPendingChanges());
689        try (BoxJSONResponse response = request.send()) {
690            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
691            info.update(jsonObject);
692        }
693    }
694
695    /**
696     * Retrieve a specific file version.
697     *
698     * @param fileVersionID the ID of the file version to retrieve.
699     * @param fields        the optional fields to retrieve.
700     * @return a specific file version.
701     */
702    public BoxFileVersion getVersionByID(String fileVersionID, String... fields) {
703        URL url = BoxFileVersion.VERSION_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), fileVersionID);
704        if (fields.length > 0) {
705            String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
706            url = BoxFileVersion.VERSION_URL_TEMPLATE.buildWithQuery(
707                this.getAPI().getBaseURL(),
708                queryString,
709                this.getID(),
710                fileVersionID
711            );
712        }
713
714        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
715        try (BoxJSONResponse response = request.send()) {
716            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
717            return new BoxFileVersion(this.getAPI(), jsonObject, this.getID());
718        }
719    }
720
721    /**
722     * Gets up to 1000 versions of this file. Note that only users with premium accounts will be able to retrieve
723     * previous versions of their files. `fields` parameter is optional, if specified only requested fields will
724     * be returned:
725     * <pre>
726     * {@code
727     * new BoxFile(api, file_id).getVersions()       // will return all default fields
728     * new BoxFile(api, file_id).getVersions("name") // will return only specified fields
729     * }
730     * </pre>
731     *
732     * @param fields the fields to retrieve. If nothing provided default fields will be returned.
733     *               You can find list of available fields at {@link BoxFile#ALL_VERSION_FIELDS}
734     * @return a list of previous file versions.
735     */
736    public Collection<BoxFileVersion> getVersions(String... fields) {
737        return getVersionsRange(0, BoxFileVersion.DEFAULT_LIMIT, fields);
738    }
739
740
741    /**
742     * Retrieves a specific range of versions of this file.
743     *
744     * @param offset the index of the first version of this file to retrieve.
745     * @param limit  the maximum number of versions to retrieve after the offset.
746     * @param fields the fields to retrieve.
747     * @return a partial collection containing the specified range of versions of this file.
748     */
749    public PartialCollection<BoxFileVersion> getVersionsRange(long offset, long limit, String... fields) {
750        QueryStringBuilder builder = new QueryStringBuilder()
751            .appendParam("limit", limit)
752            .appendParam("offset", offset);
753
754        if (fields.length > 0) {
755            builder.appendParam("fields", fields);
756        }
757
758        URL url = VERSIONS_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID());
759        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
760        try (BoxJSONResponse response = request.send()) {
761
762            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
763            String totalCountString = jsonObject.get("total_count").toString();
764            long fullSize = Double.valueOf(totalCountString).longValue();
765            PartialCollection<BoxFileVersion> versions = new PartialCollection<>(offset, limit, fullSize);
766            JsonArray entries = jsonObject.get("entries").asArray();
767            for (JsonValue entry : entries) {
768                versions.add(new BoxFileVersion(this.getAPI(), entry.asObject(), this.getID()));
769            }
770
771            return versions;
772        }
773    }
774
775    /**
776     * Checks if a new version of the file can be uploaded with the specified name.
777     *
778     * @param name the new name for the file.
779     * @return whether or not the file version can be uploaded.
780     */
781    public boolean canUploadVersion(String name) {
782        return this.canUploadVersion(name, 0);
783    }
784
785    /**
786     * Checks if a new version of the file can be uploaded with the specified name and size.
787     *
788     * @param name     the new name for the file.
789     * @param fileSize the size of the new version content in bytes.
790     * @return whether the file version can be uploaded.
791     */
792    public boolean canUploadVersion(String name, long fileSize) {
793
794        URL url = getDownloadUrl();
795        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
796
797        JsonObject preflightInfo = new JsonObject();
798        if (name != null) {
799            preflightInfo.add("name", name);
800        }
801
802        preflightInfo.add("size", fileSize);
803
804        request.setBody(preflightInfo.toString());
805        try (BoxAPIResponse response = request.send()) {
806            return response.getResponseCode() == 200;
807        } catch (BoxAPIException ex) {
808            if (ex.getResponseCode() >= 400 && ex.getResponseCode() < 500) {
809                // This looks like an error response, meaning the upload would fail
810                return false;
811            } else {
812                // This looks like a network error or server error, rethrow exception
813                throw ex;
814            }
815        }
816    }
817
818    /**
819     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
820     * will be able to view and recover previous versions of the file.
821     *
822     * @param fileContent a stream containing the new file contents.
823     * @return the uploaded file version.
824     */
825    public BoxFile.Info uploadNewVersion(InputStream fileContent) {
826        return this.uploadNewVersion(fileContent, null);
827    }
828
829    /**
830     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
831     * will be able to view and recover previous versions of the file.
832     *
833     * @param fileContent     a stream containing the new file contents.
834     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
835     * @return the uploaded file version.
836     */
837    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1) {
838        return this.uploadNewVersion(fileContent, fileContentSHA1, null);
839    }
840
841    /**
842     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
843     * will be able to view and recover previous versions of the file.
844     *
845     * @param fileContent     a stream containing the new file contents.
846     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
847     * @param modified        the date that the new version was modified.
848     * @return the uploaded file version.
849     */
850    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified) {
851        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, 0, null);
852    }
853
854    /**
855     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
856     * will be able to view and recover previous versions of the file.
857     *
858     * @param fileContent     a stream containing the new file contents.
859     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
860     * @param modified        the date that the new version was modified.
861     * @param name            the new name for the file
862     * @return the uploaded file version.
863     */
864    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, String name) {
865        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, name, 0, null);
866    }
867
868    /**
869     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
870     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
871     * of the file.
872     *
873     * @param fileContent a stream containing the new file contents.
874     * @param modified    the date that the new version was modified.
875     * @param fileSize    the size of the file used for determining the progress of the upload.
876     * @param listener    a listener for monitoring the upload's progress.
877     * @return the uploaded file version.
878     */
879    public BoxFile.Info uploadNewVersion(InputStream fileContent, Date modified, long fileSize,
880                                         ProgressListener listener) {
881        return this.uploadNewVersion(fileContent, null, modified, fileSize, listener);
882    }
883
884    /**
885     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
886     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
887     * of the file.
888     *
889     * @param fileContent     a stream containing the new file contents.
890     * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header
891     * @param modified        the date that the new version was modified.
892     * @param fileSize        the size of the file used for determining the progress of the upload.
893     * @param listener        a listener for monitoring the upload's progress.
894     * @return the uploaded file version.
895     */
896    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, long fileSize,
897                                         ProgressListener listener) {
898        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, null, fileSize, listener);
899    }
900
901    /**
902     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
903     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
904     * of the file.
905     *
906     * @param fileContent     a stream containing the new file contents.
907     * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header
908     * @param modified        the date that the new version was modified.
909     * @param name            the new name for the file
910     * @param fileSize        the size of the file used for determining the progress of the upload.
911     * @param listener        a listener for monitoring the upload's progress.
912     * @return the uploaded file version.
913     */
914    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, String name,
915                                         long fileSize, ProgressListener listener) {
916        URL uploadURL = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
917        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
918
919        if (fileSize > 0) {
920            request.setFile(fileContent, "", fileSize);
921        } else {
922            request.setFile(fileContent, "");
923        }
924
925        if (fileContentSHA1 != null) {
926            request.setContentSHA1(fileContentSHA1);
927        }
928
929        JsonObject attributesJSON = new JsonObject();
930        if (modified != null) {
931            attributesJSON.add("content_modified_at", BoxDateFormat.format(modified));
932        }
933
934        if (name != null) {
935            attributesJSON.add("name", name);
936        }
937
938        request.putField("attributes", attributesJSON.toString());
939
940        BoxJSONResponse response = null;
941        try {
942            if (listener == null) {
943                // upload is multipart request but response is JSON
944                response = (BoxJSONResponse) request.send();
945            } else {
946                // upload is multipart request but response is JSON
947                response = (BoxJSONResponse) request.send(listener);
948            }
949
950            String fileJSON = response.getJsonObject().get("entries").asArray().get(0).toString();
951
952            return new BoxFile.Info(fileJSON);
953        } finally {
954            Optional.ofNullable(response).ifPresent(BoxAPIResponse::close);
955        }
956    }
957
958    /**
959     * Gets an expiring URL for creating an embedded preview session. The URL will expire after 60 seconds and the
960     * preview session will expire after 60 minutes.
961     *
962     * @return the expiring preview link
963     */
964    public URL getPreviewLink() {
965        BoxFile.Info info = this.getInfo("expiring_embed_link");
966
967        return info.getPreviewLink();
968    }
969
970    /**
971     * Gets a list of any comments on this file.
972     *
973     * @return a list of comments on this file.
974     */
975    public List<BoxComment.Info> getComments() {
976        URL url = GET_COMMENTS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
977        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
978        try (BoxJSONResponse response = request.send()) {
979            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
980
981            int totalCount = responseJSON.get("total_count").asInt();
982            List<BoxComment.Info> comments = new ArrayList<>(totalCount);
983            JsonArray entries = responseJSON.get("entries").asArray();
984            for (JsonValue value : entries) {
985                JsonObject commentJSON = value.asObject();
986                BoxComment comment = new BoxComment(this.getAPI(), commentJSON.get("id").asString());
987                BoxComment.Info info = comment.new Info(commentJSON);
988                comments.add(info);
989            }
990
991            return comments;
992        }
993    }
994
995    /**
996     * Gets a list of any tasks on this file with requested fields.
997     *
998     * @param fields optional fields to retrieve for this task.
999     * @return a list of tasks on this file.
1000     */
1001    public List<BoxTask.Info> getTasks(String... fields) {
1002        QueryStringBuilder builder = new QueryStringBuilder();
1003        if (fields.length > 0) {
1004            builder.appendParam("fields", fields);
1005        }
1006        URL url = GET_TASKS_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID());
1007        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1008        try (BoxJSONResponse response = request.send()) {
1009            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1010
1011            int totalCount = responseJSON.get("total_count").asInt();
1012            List<BoxTask.Info> tasks = new ArrayList<>(totalCount);
1013            JsonArray entries = responseJSON.get("entries").asArray();
1014            for (JsonValue value : entries) {
1015                JsonObject taskJSON = value.asObject();
1016                BoxTask task = new BoxTask(this.getAPI(), taskJSON.get("id").asString());
1017                BoxTask.Info info = task.new Info(taskJSON);
1018                tasks.add(info);
1019            }
1020
1021            return tasks;
1022        }
1023    }
1024
1025    /**
1026     * Creates metadata on this file in the global properties template.
1027     *
1028     * @param metadata The new metadata values.
1029     * @return the metadata returned from the server.
1030     */
1031    public Metadata createMetadata(Metadata metadata) {
1032        return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata);
1033    }
1034
1035    /**
1036     * Creates metadata on this file in the specified template type.
1037     *
1038     * @param typeName the metadata template type name.
1039     * @param metadata the new metadata values.
1040     * @return the metadata returned from the server.
1041     */
1042    public Metadata createMetadata(String typeName, Metadata metadata) {
1043        String scope = Metadata.scopeBasedOnType(typeName);
1044        return this.createMetadata(typeName, scope, metadata);
1045    }
1046
1047    /**
1048     * Creates metadata on this file in the specified template type.
1049     *
1050     * @param typeName the metadata template type name.
1051     * @param scope    the metadata scope (global or enterprise).
1052     * @param metadata the new metadata values.
1053     * @return the metadata returned from the server.
1054     */
1055    public Metadata createMetadata(String typeName, String scope, Metadata metadata) {
1056        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1057        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1058        request.setBody(metadata.toString());
1059        try (BoxJSONResponse response = request.send()) {
1060            return new Metadata(Json.parse(response.getJSON()).asObject());
1061        }
1062    }
1063
1064    /**
1065     * Sets the provided metadata on the file. If metadata has already been created on this file,
1066     * it overwrites metadata keys specified in the `metadata` param.
1067     *
1068     * @param templateName the name of the metadata template.
1069     * @param scope        the scope of the template (usually "global" or "enterprise").
1070     * @param metadata     the new metadata values.
1071     * @return the metadata returned from the server.
1072     */
1073    public Metadata setMetadata(String templateName, String scope, Metadata metadata) {
1074        try {
1075            return this.createMetadata(templateName, scope, metadata);
1076        } catch (BoxAPIException e) {
1077            if (e.getResponseCode() == 409) {
1078                if (metadata.getOperations().isEmpty()) {
1079                    return getMetadata();
1080                } else {
1081                    return updateExistingTemplate(templateName, scope, metadata);
1082                }
1083            } else {
1084                throw e;
1085            }
1086        }
1087    }
1088
1089    private Metadata updateExistingTemplate(String templateName, String scope, Metadata metadata) {
1090        Metadata metadataToUpdate = new Metadata(scope, templateName);
1091        for (JsonValue value : metadata.getOperations()) {
1092            if (value.asObject().get("value").isNumber()) {
1093                metadataToUpdate.add(value.asObject().get("path").asString(),
1094                    value.asObject().get("value").asDouble());
1095            } else if (value.asObject().get("value").isString()) {
1096                metadataToUpdate.add(value.asObject().get("path").asString(),
1097                    value.asObject().get("value").asString());
1098            } else if (value.asObject().get("value").isArray()) {
1099                ArrayList<String> list = new ArrayList<>();
1100                for (JsonValue jsonValue : value.asObject().get("value").asArray()) {
1101                    list.add(jsonValue.asString());
1102                }
1103                metadataToUpdate.add(value.asObject().get("path").asString(), list);
1104            }
1105        }
1106        return this.updateMetadata(metadataToUpdate);
1107    }
1108
1109    /**
1110     * Adds a metadata classification to the specified file.
1111     *
1112     * @param classificationType the metadata classification type.
1113     * @return the metadata classification type added to the file.
1114     */
1115    public String addClassification(String classificationType) {
1116        Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType);
1117        Metadata classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY,
1118            "enterprise", metadata);
1119
1120        return classification.getString(Metadata.CLASSIFICATION_KEY);
1121    }
1122
1123    /**
1124     * Updates a metadata classification on the specified file.
1125     *
1126     * @param classificationType the metadata classification type.
1127     * @return the new metadata classification type updated on the file.
1128     */
1129    public String updateClassification(String classificationType) {
1130        Metadata metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY);
1131        metadata.add("/Box__Security__Classification__Key", classificationType);
1132        Metadata classification = this.updateMetadata(metadata);
1133
1134        return classification.getString(Metadata.CLASSIFICATION_KEY);
1135    }
1136
1137    /**
1138     * Attempts to add classification to a file. If classification already exists then do update.
1139     *
1140     * @param classificationType the metadata classification type.
1141     * @return the metadata classification type on the file.
1142     */
1143    public String setClassification(String classificationType) {
1144        Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType);
1145        Metadata classification;
1146
1147        try {
1148            classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise", metadata);
1149        } catch (BoxAPIException e) {
1150            if (e.getResponseCode() == 409) {
1151                metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY);
1152                metadata.replace(Metadata.CLASSIFICATION_KEY, classificationType);
1153                classification = this.updateMetadata(metadata);
1154            } else {
1155                throw e;
1156            }
1157        }
1158
1159        return classification.getString(Metadata.CLASSIFICATION_KEY);
1160    }
1161
1162    /**
1163     * Gets the classification type for the specified file.
1164     *
1165     * @return the metadata classification type on the file.
1166     */
1167    public String getClassification() {
1168        Metadata metadata;
1169        try {
1170            metadata = this.getMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY);
1171
1172        } catch (BoxAPIException e) {
1173            JsonObject responseObject = Json.parse(e.getResponse()).asObject();
1174            String code = responseObject.get("code").asString();
1175
1176            if (e.getResponseCode() == 404 && code.equals("instance_not_found")) {
1177                return null;
1178            } else {
1179                throw e;
1180            }
1181        }
1182
1183        return metadata.getString(Metadata.CLASSIFICATION_KEY);
1184    }
1185
1186    /**
1187     * Deletes the classification on the file.
1188     */
1189    public void deleteClassification() {
1190        this.deleteMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise");
1191    }
1192
1193    /**
1194     * Locks a file.
1195     *
1196     * @return the lock returned from the server.
1197     */
1198    public BoxLock lock() {
1199        return this.lock(null, false);
1200    }
1201
1202    /**
1203     * Locks a file.
1204     *
1205     * @param isDownloadPrevented is downloading of file prevented when locked.
1206     * @return the lock returned from the server.
1207     */
1208    public BoxLock lock(boolean isDownloadPrevented) {
1209        return this.lock(null, isDownloadPrevented);
1210    }
1211
1212    /**
1213     * Locks a file.
1214     *
1215     * @param expiresAt expiration date of the lock.
1216     * @return the lock returned from the server.
1217     */
1218    public BoxLock lock(Date expiresAt) {
1219        return this.lock(expiresAt, false);
1220    }
1221
1222    /**
1223     * Locks a file.
1224     *
1225     * @param expiresAt           expiration date of the lock.
1226     * @param isDownloadPrevented is downloading of file prevented when locked.
1227     * @return the lock returned from the server.
1228     */
1229    public BoxLock lock(Date expiresAt, boolean isDownloadPrevented) {
1230        String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString();
1231        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1232        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
1233
1234        JsonObject lockConfig = new JsonObject();
1235        lockConfig.add("type", "lock");
1236        if (expiresAt != null) {
1237            lockConfig.add("expires_at", BoxDateFormat.format(expiresAt));
1238        }
1239        lockConfig.add("is_download_prevented", isDownloadPrevented);
1240
1241        JsonObject requestJSON = new JsonObject();
1242        requestJSON.add("lock", lockConfig);
1243        request.setBody(requestJSON.toString());
1244
1245        try (BoxJSONResponse response = request.send()) {
1246
1247            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1248            JsonValue lockValue = responseJSON.get("lock");
1249            JsonObject lockJSON = Json.parse(lockValue.toString()).asObject();
1250
1251            return new BoxLock(lockJSON, this.getAPI());
1252        }
1253    }
1254
1255    /**
1256     * Unlocks a file.
1257     */
1258    public void unlock() {
1259        String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString();
1260        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1261        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
1262
1263        JsonObject lockObject = new JsonObject();
1264        lockObject.add("lock", NULL);
1265
1266        request.setBody(lockObject.toString());
1267        request.send().close();
1268    }
1269
1270    /**
1271     * Used to retrieve all metadata associated with the file.
1272     *
1273     * @param fields the optional fields to retrieve.
1274     * @return An iterable of metadata instances associated with the file.
1275     */
1276    public Iterable<Metadata> getAllMetadata(String... fields) {
1277        return Metadata.getAllMetadata(this, fields);
1278    }
1279
1280    /**
1281     * Gets the file properties metadata.
1282     *
1283     * @return the metadata returned from the server.
1284     */
1285    public Metadata getMetadata() {
1286        return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE);
1287    }
1288
1289    /**
1290     * Gets the file metadata of specified template type.
1291     *
1292     * @param typeName the metadata template type name.
1293     * @return the metadata returned from the server.
1294     */
1295    public Metadata getMetadata(String typeName) {
1296        String scope = Metadata.scopeBasedOnType(typeName);
1297        return this.getMetadata(typeName, scope);
1298    }
1299
1300    /**
1301     * Gets the file metadata of specified template type.
1302     *
1303     * @param typeName the metadata template type name.
1304     * @param scope    the metadata scope (global or enterprise).
1305     * @return the metadata returned from the server.
1306     */
1307    public Metadata getMetadata(String typeName, String scope) {
1308        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1309        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1310        try (BoxJSONResponse response = request.send()) {
1311            return new Metadata(Json.parse(response.getJSON()).asObject());
1312        }
1313    }
1314
1315    /**
1316     * Updates the file metadata.
1317     *
1318     * @param metadata the new metadata values.
1319     * @return the metadata returned from the server.
1320     */
1321    public Metadata updateMetadata(Metadata metadata) {
1322        String scope;
1323        if (metadata.getScope().equals(Metadata.GLOBAL_METADATA_SCOPE)) {
1324            scope = Metadata.GLOBAL_METADATA_SCOPE;
1325        } else if (metadata.getScope().startsWith(Metadata.ENTERPRISE_METADATA_SCOPE)) {
1326            scope = metadata.getScope();
1327        } else {
1328            scope = Metadata.ENTERPRISE_METADATA_SCOPE;
1329        }
1330
1331        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(),
1332            scope, metadata.getTemplateName());
1333        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT", APPLICATION_JSON_PATCH);
1334        request.setBody(metadata.getPatch());
1335        try (BoxJSONResponse response = request.send()) {
1336            return new Metadata(Json.parse(response.getJSON()).asObject());
1337        }
1338    }
1339
1340    /**
1341     * Deletes the file properties metadata.
1342     */
1343    public void deleteMetadata() {
1344        this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE);
1345    }
1346
1347    /**
1348     * Deletes the file metadata of specified template type.
1349     *
1350     * @param typeName the metadata template type name.
1351     */
1352    public void deleteMetadata(String typeName) {
1353        String scope = Metadata.scopeBasedOnType(typeName);
1354        this.deleteMetadata(typeName, scope);
1355    }
1356
1357    /**
1358     * Deletes the file metadata of specified template type.
1359     *
1360     * @param typeName the metadata template type name.
1361     * @param scope    the metadata scope (global or enterprise).
1362     */
1363    public void deleteMetadata(String typeName, String scope) {
1364        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1365        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
1366        request.send().close();
1367    }
1368
1369    /**
1370     * Used to retrieve the watermark for the file.
1371     * If the file does not have a watermark applied to it, a 404 Not Found will be returned by API.
1372     *
1373     * @param fields the fields to retrieve.
1374     * @return the watermark associated with the file.
1375     */
1376    public BoxWatermark getWatermark(String... fields) {
1377        return this.getWatermark(FILE_URL_TEMPLATE, fields);
1378    }
1379
1380    /**
1381     * Used to apply or update the watermark for the file.
1382     *
1383     * @return the watermark associated with the file.
1384     */
1385    public BoxWatermark applyWatermark() {
1386        return this.applyWatermark(FILE_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT);
1387    }
1388
1389    /**
1390     * Removes a watermark from the file.
1391     * If the file did not have a watermark applied to it, a 404 Not Found will be returned by API.
1392     */
1393    public void removeWatermark() {
1394        this.removeWatermark(FILE_URL_TEMPLATE);
1395    }
1396
1397    /**
1398     * {@inheritDoc}
1399     */
1400    @Override
1401    public BoxFile.Info setCollections(BoxCollection... collections) {
1402        JsonArray jsonArray = new JsonArray();
1403        for (BoxCollection collection : collections) {
1404            JsonObject collectionJSON = new JsonObject();
1405            collectionJSON.add("id", collection.getID());
1406            jsonArray.add(collectionJSON);
1407        }
1408        JsonObject infoJSON = new JsonObject();
1409        infoJSON.add("collections", jsonArray);
1410
1411        String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString();
1412        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1413        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
1414        request.setBody(infoJSON.toString());
1415        try (BoxJSONResponse response = request.send()) {
1416            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
1417            return new Info(jsonObject);
1418        }
1419    }
1420
1421    /**
1422     * Creates an upload session to create a new version of a file in chunks.
1423     * This will first verify that the version can be created and then open a session for uploading pieces of the file.
1424     *
1425     * @param fileSize the size of the file that will be uploaded.
1426     * @return the created upload session instance.
1427     */
1428    public BoxFileUploadSession.Info createUploadSession(long fileSize) {
1429        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1430
1431        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1432        request.addHeader("Content-Type", APPLICATION_JSON);
1433
1434        JsonObject body = new JsonObject();
1435        body.add("file_size", fileSize);
1436        request.setBody(body.toString());
1437
1438        try (BoxJSONResponse response = request.send()) {
1439            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
1440
1441            String sessionId = jsonObject.get("id").asString();
1442            BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId);
1443            return session.new Info(jsonObject);
1444        }
1445    }
1446
1447    /**
1448     * Creates a new version of a file.
1449     *
1450     * @param inputStream the stream instance that contains the data.
1451     * @param fileSize    the size of the file that will be uploaded.
1452     * @return the created file instance.
1453     * @throws InterruptedException when a thread execution is interrupted.
1454     * @throws IOException          when reading a stream throws exception.
1455     */
1456    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize)
1457        throws InterruptedException, IOException {
1458        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1459        return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize);
1460    }
1461
1462    /**
1463     * Creates a new version of a file.  Also sets file attributes.
1464     *
1465     * @param inputStream    the stream instance that contains the data.
1466     * @param fileSize       the size of the file that will be uploaded.
1467     * @param fileAttributes file attributes to set
1468     * @return the created file instance.
1469     * @throws InterruptedException when a thread execution is interrupted.
1470     * @throws IOException          when reading a stream throws exception.
1471     */
1472    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize, Map<String, String> fileAttributes)
1473        throws InterruptedException, IOException {
1474        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1475        return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize, fileAttributes);
1476    }
1477
1478    /**
1479     * Creates a new version of a file using specified number of parallel http connections.
1480     *
1481     * @param inputStream          the stream instance that contains the data.
1482     * @param fileSize             the size of the file that will be uploaded.
1483     * @param nParallelConnections number of parallel http connections to use
1484     * @param timeOut              time to wait before killing the job
1485     * @param unit                 time unit for the time wait value
1486     * @return the created file instance.
1487     * @throws InterruptedException when a thread execution is interrupted.
1488     * @throws IOException          when reading a stream throws exception.
1489     */
1490    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize,
1491                                        int nParallelConnections, long timeOut, TimeUnit unit)
1492        throws InterruptedException, IOException {
1493        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1494        return new LargeFileUpload(nParallelConnections, timeOut, unit)
1495            .upload(this.getAPI(), inputStream, url, fileSize);
1496    }
1497
1498    /**
1499     * Creates a new version of a file using specified number of parallel http connections.  Also sets file attributes.
1500     *
1501     * @param inputStream          the stream instance that contains the data.
1502     * @param fileSize             the size of the file that will be uploaded.
1503     * @param nParallelConnections number of parallel http connections to use
1504     * @param timeOut              time to wait before killing the job
1505     * @param unit                 time unit for the time wait value
1506     * @param fileAttributes       file attributes to set
1507     * @return the created file instance.
1508     * @throws InterruptedException when a thread execution is interrupted.
1509     * @throws IOException          when reading a stream throws exception.
1510     */
1511    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize,
1512                                        int nParallelConnections, long timeOut, TimeUnit unit,
1513                                        Map<String, String> fileAttributes)
1514        throws InterruptedException, IOException {
1515        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1516        return new LargeFileUpload(nParallelConnections, timeOut, unit)
1517            .upload(this.getAPI(), inputStream, url, fileSize, fileAttributes);
1518    }
1519
1520    private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role,
1521                                              Boolean notify, Boolean canViewPath, Date expiresAt,
1522                                              Boolean isAccessOnly) {
1523
1524        JsonObject itemField = new JsonObject();
1525        itemField.add("id", this.getID());
1526        itemField.add("type", "file");
1527
1528        return BoxCollaboration.create(this.getAPI(), accessibleByField, itemField, role, notify, canViewPath,
1529            expiresAt, isAccessOnly);
1530    }
1531
1532    /**
1533     * Adds a collaborator to this file.
1534     *
1535     * @param collaborator the collaborator to add.
1536     * @param role         the role of the collaborator.
1537     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1538     * @param canViewPath  whether view path collaboration feature is enabled or not.
1539     * @param expiresAt    when the collaboration should expire.
1540     * @param isAccessOnly whether the collaboration is access only or not.
1541     * @return info about the new collaboration.
1542     */
1543    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role,
1544                                             Boolean notify, Boolean canViewPath,
1545                                             Date expiresAt, Boolean isAccessOnly) {
1546        JsonObject accessibleByField = new JsonObject();
1547        accessibleByField.add("id", collaborator.getID());
1548
1549        if (collaborator instanceof BoxUser) {
1550            accessibleByField.add("type", "user");
1551        } else if (collaborator instanceof BoxGroup) {
1552            accessibleByField.add("type", "group");
1553        } else {
1554            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
1555        }
1556        return this.collaborate(accessibleByField, role, notify, canViewPath, expiresAt, isAccessOnly);
1557    }
1558
1559    /**
1560     * Adds a collaborator to this file.
1561     *
1562     * @param collaborator the collaborator to add.
1563     * @param role         the role of the collaborator.
1564     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1565     * @param canViewPath  whether view path collaboration feature is enabled or not.
1566     * @return info about the new collaboration.
1567     */
1568    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role,
1569                                             Boolean notify, Boolean canViewPath) {
1570        return this.collaborate(collaborator, role, notify, canViewPath, null, null);
1571    }
1572
1573    /**
1574     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
1575     * account.
1576     *
1577     * @param email        the email address of the collaborator to add.
1578     * @param role         the role of the collaborator.
1579     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1580     * @param canViewPath  whether view path collaboration feature is enabled or not.
1581     * @param expiresAt    when the collaboration should expire.
1582     * @param isAccessOnly whether the collaboration is access only or not.
1583     * @return info about the new collaboration.
1584     */
1585    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role,
1586                                             Boolean notify, Boolean canViewPath,
1587                                             Date expiresAt, Boolean isAccessOnly) {
1588        JsonObject accessibleByField = new JsonObject();
1589        accessibleByField.add("login", email);
1590        accessibleByField.add("type", "user");
1591
1592        return this.collaborate(accessibleByField, role, notify, canViewPath, expiresAt, isAccessOnly);
1593    }
1594
1595    /**
1596     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
1597     * account.
1598     *
1599     * @param email       the email address of the collaborator to add.
1600     * @param role        the role of the collaborator.
1601     * @param notify      determines if the user (or all the users in the group) will receive email notifications.
1602     * @param canViewPath whether view path collaboration feature is enabled or not.
1603     * @return info about the new collaboration.
1604     */
1605    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role,
1606                                             Boolean notify, Boolean canViewPath) {
1607        return this.collaborate(email, role, notify, canViewPath, null, null);
1608    }
1609
1610    /**
1611     * Used to retrieve all collaborations associated with the item.
1612     *
1613     * @param fields the optional fields to retrieve.
1614     * @return An iterable of metadata instances associated with the item.
1615     */
1616    public BoxResourceIterable<BoxCollaboration.Info> getAllFileCollaborations(String... fields) {
1617        return BoxCollaboration.getAllFileCollaborations(this.getAPI(), this.getID(),
1618            GET_COLLABORATORS_PAGE_SIZE, fields);
1619
1620    }
1621
1622    /**
1623     * Used to specify what filetype to request for a file thumbnail.
1624     */
1625    public enum ThumbnailFileType {
1626        /**
1627         * PNG image format.
1628         */
1629        PNG,
1630
1631        /**
1632         * JPG image format.
1633         */
1634        JPG
1635    }
1636
1637    /**
1638     * Enumerates the possible permissions that a user can have on a file.
1639     */
1640    public enum Permission {
1641        /**
1642         * The user can download the file.
1643         */
1644        CAN_DOWNLOAD("can_download"),
1645
1646        /**
1647         * The user can upload new versions of the file.
1648         */
1649        CAN_UPLOAD("can_upload"),
1650
1651        /**
1652         * The user can rename the file.
1653         */
1654        CAN_RENAME("can_rename"),
1655
1656        /**
1657         * The user can delete the file.
1658         */
1659        CAN_DELETE("can_delete"),
1660
1661        /**
1662         * The user can share the file.
1663         */
1664        CAN_SHARE("can_share"),
1665
1666        /**
1667         * The user can set the access level for shared links to the file.
1668         */
1669        CAN_SET_SHARE_ACCESS("can_set_share_access"),
1670
1671        /**
1672         * The user can preview the file.
1673         */
1674        CAN_PREVIEW("can_preview"),
1675
1676        /**
1677         * The user can comment on the file.
1678         */
1679        CAN_COMMENT("can_comment"),
1680
1681        /**
1682         * The user can place annotations on this file.
1683         */
1684        CAN_ANNOTATE("can_annotate"),
1685
1686        /**
1687         * The current user can invite new users to collaborate on this item, and the user can update the role of a
1688         * user already collaborated on this item.
1689         */
1690        CAN_INVITE_COLLABORATOR("can_invite_collaborator"),
1691
1692        /**
1693         * The user can view all annotations placed on this file.
1694         */
1695        CAN_VIEW_ANNOTATIONS_ALL("can_view_annotations_all"),
1696
1697        /**
1698         * The user can view annotations placed by themselves on this file.
1699         */
1700        CAN_VIEW_ANNOTATIONS_SELF("can_view_annotations_self");
1701
1702        private final String jsonValue;
1703
1704        Permission(String jsonValue) {
1705            this.jsonValue = jsonValue;
1706        }
1707
1708        static Permission fromJSONValue(String jsonValue) {
1709            return Permission.valueOf(jsonValue.toUpperCase());
1710        }
1711
1712        String toJSONValue() {
1713            return this.jsonValue;
1714        }
1715    }
1716
1717    /**
1718     * Contains information about a BoxFile.
1719     */
1720    public class Info extends BoxItem.Info {
1721        private String sha1;
1722        private String versionNumber;
1723        private long commentCount;
1724        private EnumSet<Permission> permissions;
1725        private String extension;
1726        private boolean isPackage;
1727        private BoxFileVersion version;
1728        private URL previewLink;
1729        private BoxLock lock;
1730        private boolean isWatermarked;
1731        private boolean isExternallyOwned;
1732        private Map<String, Map<String, Metadata>> metadataMap;
1733        private List<Representation> representations;
1734        private List<String> allowedInviteeRoles;
1735        private Boolean hasCollaborations;
1736        private String uploaderDisplayName;
1737        private BoxClassification classification;
1738        private Date dispositionAt;
1739        private boolean isAccessibleViaSharedLink;
1740
1741        /**
1742         * Constructs an empty Info object.
1743         */
1744        public Info() {
1745            super();
1746        }
1747
1748        /**
1749         * Constructs an Info object by parsing information from a JSON string.
1750         *
1751         * @param json the JSON string to parse.
1752         */
1753        public Info(String json) {
1754            super(json);
1755        }
1756
1757        /**
1758         * Constructs an Info object using an already parsed JSON object.
1759         *
1760         * @param jsonObject the parsed JSON object.
1761         */
1762        public Info(JsonObject jsonObject) {
1763            super(jsonObject);
1764        }
1765
1766        @Override
1767        public BoxFile getResource() {
1768            return BoxFile.this;
1769        }
1770
1771        /**
1772         * Gets the SHA1 hash of the file.
1773         *
1774         * @return the SHA1 hash of the file.
1775         */
1776        public String getSha1() {
1777            return this.sha1;
1778        }
1779
1780        /**
1781         * Gets the lock of the file.
1782         *
1783         * @return the lock of the file.
1784         */
1785        public BoxLock getLock() {
1786            return this.lock;
1787        }
1788
1789        /**
1790         * Gets the current version number of the file.
1791         *
1792         * @return the current version number of the file.
1793         */
1794        public String getVersionNumber() {
1795            return this.versionNumber;
1796        }
1797
1798        /**
1799         * Gets the number of comments on the file.
1800         *
1801         * @return the number of comments on the file.
1802         */
1803        public long getCommentCount() {
1804            return this.commentCount;
1805        }
1806
1807        /**
1808         * Gets the permissions that the current user has on the file.
1809         *
1810         * @return the permissions that the current user has on the file.
1811         */
1812        public EnumSet<Permission> getPermissions() {
1813            return this.permissions;
1814        }
1815
1816        /**
1817         * Gets the extension suffix of the file, excluding the dot.
1818         *
1819         * @return the extension of the file.
1820         */
1821        public String getExtension() {
1822            return this.extension;
1823        }
1824
1825        /**
1826         * Gets whether or not the file is an OSX package.
1827         *
1828         * @return true if the file is an OSX package; otherwise false.
1829         */
1830        public boolean getIsPackage() {
1831            return this.isPackage;
1832        }
1833
1834        /**
1835         * Gets the current version details of the file.
1836         *
1837         * @return the current version details of the file.
1838         */
1839        public BoxFileVersion getVersion() {
1840            return this.version;
1841        }
1842
1843        /**
1844         * Gets the current expiring preview link.
1845         *
1846         * @return the expiring preview link
1847         */
1848        public URL getPreviewLink() {
1849            return this.previewLink;
1850        }
1851
1852        /**
1853         * Gets flag indicating whether this file is Watermarked.
1854         *
1855         * @return whether the file is watermarked or not
1856         */
1857        public boolean getIsWatermarked() {
1858            return this.isWatermarked;
1859        }
1860
1861        /**
1862         * Returns the allowed invitee roles for this file item.
1863         *
1864         * @return the list of roles allowed for invited collaborators.
1865         */
1866        public List<String> getAllowedInviteeRoles() {
1867            return this.allowedInviteeRoles;
1868        }
1869
1870        /**
1871         * Returns the indicator for whether this file item has collaborations.
1872         *
1873         * @return indicator for whether this file item has collaborations.
1874         */
1875        public Boolean getHasCollaborations() {
1876            return this.hasCollaborations;
1877        }
1878
1879        /**
1880         * Gets the metadata on this file associated with a specified scope and template.
1881         * Makes an attempt to get metadata that was retrieved using getInfo(String ...) method.
1882         *
1883         * @param templateName the metadata template type name.
1884         * @param scope        the scope of the template (usually "global" or "enterprise").
1885         * @return the metadata returned from the server.
1886         */
1887        public Metadata getMetadata(String templateName, String scope) {
1888            try {
1889                return this.metadataMap.get(scope).get(templateName);
1890            } catch (NullPointerException e) {
1891                return null;
1892            }
1893        }
1894
1895        /**
1896         * Returns the field for indicating whether a file is owned by a user outside the enterprise.
1897         *
1898         * @return indicator for whether or not the file is owned by a user outside the enterprise.
1899         */
1900        public boolean getIsExternallyOwned() {
1901            return this.isExternallyOwned;
1902        }
1903
1904        /**
1905         * Get file's representations.
1906         *
1907         * @return list of representations
1908         */
1909        public List<Representation> getRepresentations() {
1910            return this.representations;
1911        }
1912
1913        /**
1914         * Returns user's name at the time of upload.
1915         *
1916         * @return user's name at the time of upload
1917         */
1918        public String getUploaderDisplayName() {
1919            return this.uploaderDisplayName;
1920        }
1921
1922        /**
1923         * Gets the metadata classification type of this file.
1924         *
1925         * @return the metadata classification type of this file.
1926         */
1927        public BoxClassification getClassification() {
1928            return this.classification;
1929        }
1930
1931        /**
1932         * Returns the retention expiration timestamp for the given file.
1933         *
1934         * @return Date representing expiration timestamp
1935         */
1936        public Date getDispositionAt() {
1937            return dispositionAt;
1938        }
1939
1940        /**
1941         * Modifies the retention expiration timestamp for the given file.
1942         * This date cannot be shortened once set on a file.
1943         *
1944         * @param dispositionAt Date representing expiration timestamp
1945         */
1946        public void setDispositionAt(Date dispositionAt) {
1947            this.dispositionAt = dispositionAt;
1948            this.addPendingChange("disposition_at", BoxDateFormat.format(dispositionAt));
1949        }
1950
1951        /**
1952         * Returns the flag indicating whether the file is accessible via a shared link.
1953         *
1954         * @return boolean flag indicating whether the file is accessible via a shared link.
1955         */
1956        public boolean getIsAccessibleViaSharedLink() {
1957            return this.isAccessibleViaSharedLink;
1958        }
1959
1960        @Override
1961        protected void parseJSONMember(JsonObject.Member member) {
1962            super.parseJSONMember(member);
1963
1964            String memberName = member.getName();
1965            JsonValue value = member.getValue();
1966            try {
1967                switch (memberName) {
1968                    case "sha1":
1969                        this.sha1 = value.asString();
1970                        break;
1971                    case "version_number":
1972                        this.versionNumber = value.asString();
1973                        break;
1974                    case "comment_count":
1975                        this.commentCount = value.asLong();
1976                        break;
1977                    case "permissions":
1978                        this.permissions = this.parsePermissions(value.asObject());
1979                        break;
1980                    case "extension":
1981                        this.extension = value.asString();
1982                        break;
1983                    case "is_package":
1984                        this.isPackage = value.asBoolean();
1985                        break;
1986                    case "has_collaborations":
1987                        this.hasCollaborations = value.asBoolean();
1988                        break;
1989                    case "is_externally_owned":
1990                        this.isExternallyOwned = value.asBoolean();
1991                        break;
1992                    case "file_version":
1993                        this.version = this.parseFileVersion(value.asObject());
1994                        break;
1995                    case "allowed_invitee_roles":
1996                        this.allowedInviteeRoles = this.parseAllowedInviteeRoles(value.asArray());
1997                        break;
1998                    case "expiring_embed_link":
1999                        try {
2000                            String urlString = member.getValue().asObject().get("url").asString();
2001                            this.previewLink = new URL(urlString);
2002                        } catch (MalformedURLException e) {
2003                            throw new BoxAPIException("Couldn't parse expiring_embed_link/url for file", e);
2004                        }
2005                        break;
2006                    case "lock":
2007                        if (value.isNull()) {
2008                            this.lock = null;
2009                        } else {
2010                            this.lock = new BoxLock(value.asObject(), BoxFile.this.getAPI());
2011                        }
2012                        break;
2013                    case "watermark_info":
2014                        this.isWatermarked = value.asObject().get("is_watermarked").asBoolean();
2015                        break;
2016                    case "metadata":
2017                        this.metadataMap = Parsers.parseAndPopulateMetadataMap(value.asObject());
2018                        break;
2019                    case "representations":
2020                        this.representations = Parsers.parseRepresentations(value.asObject());
2021                        break;
2022                    case "uploader_display_name":
2023                        this.uploaderDisplayName = value.asString();
2024                        break;
2025                    case "classification":
2026                        if (value.isNull()) {
2027                            this.classification = null;
2028                        } else {
2029                            this.classification = new BoxClassification(value.asObject());
2030                        }
2031                        break;
2032                    case "disposition_at":
2033                        this.dispositionAt = BoxDateFormat.parse(value.asString());
2034                        break;
2035                    case "is_accessible_via_shared_link":
2036                        this.isAccessibleViaSharedLink = value.asBoolean();
2037                        break;
2038                    default:
2039                        break;
2040                }
2041            } catch (Exception e) {
2042                throw new BoxDeserializationException(memberName, value.toString(), e);
2043            }
2044        }
2045
2046        @SuppressWarnings("checkstyle:MissingSwitchDefault")
2047        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
2048            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
2049            for (JsonObject.Member member : jsonObject) {
2050                JsonValue value = member.getValue();
2051                if (value.isNull() || !value.asBoolean()) {
2052                    continue;
2053                }
2054                try {
2055                    permissions.add(Permission.fromJSONValue(member.getName()));
2056                } catch (IllegalArgumentException ignored) {
2057                    // If the permission is not recognized, we ignore it.
2058                }
2059            }
2060
2061            return permissions;
2062        }
2063
2064        private BoxFileVersion parseFileVersion(JsonObject jsonObject) {
2065            return new BoxFileVersion(BoxFile.this.getAPI(), jsonObject, BoxFile.this.getID());
2066        }
2067
2068        private List<String> parseAllowedInviteeRoles(JsonArray jsonArray) {
2069            List<String> roles = new ArrayList<>(jsonArray.size());
2070            for (JsonValue value : jsonArray) {
2071                roles.add(value.asString());
2072            }
2073
2074            return roles;
2075        }
2076    }
2077
2078}