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