001package com.box.sdk;
002
003import java.io.ByteArrayOutputStream;
004import java.io.IOException;
005import java.io.InputStream;
006import java.io.OutputStream;
007import java.net.MalformedURLException;
008import java.net.URL;
009import java.util.ArrayList;
010import java.util.Collection;
011import java.util.Date;
012import java.util.EnumSet;
013import java.util.List;
014
015import com.eclipsesource.json.JsonArray;
016import com.eclipsesource.json.JsonObject;
017import com.eclipsesource.json.JsonValue;
018
019
020/**
021 * Represents an individual file on Box. This class can be used to download a file's contents, upload new versions, and
022 * perform other common file operations (move, copy, delete, etc.).
023 *
024 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked
025 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error
026 * handling for errors related to the Box REST API, you should capture this exception explicitly.</p>
027 */
028public class BoxFile extends BoxItem {
029
030    /**
031     * An array of all possible file fields that can be requested when calling {@link #getInfo()}.
032     */
033    public static final String[] ALL_FIELDS = {"type", "id", "sequence_id", "etag", "sha1", "name",
034        "description", "size", "path_collection", "created_at", "modified_at", "trashed_at", "purged_at",
035        "content_created_at", "content_modified_at", "created_by", "modified_by", "owned_by", "shared_link", "parent",
036        "item_status", "version_number", "comment_count", "permissions", "tags", "lock", "extension", "is_package",
037        "file_version"};
038
039    /**
040     * Used to specify what filetype to request for a file thumbnail.
041     */
042    public enum ThumbnailFileType {
043        /**
044         * PNG image format.
045         */
046        PNG,
047
048        /**
049         * JPG image format.
050         */
051        JPG
052    }
053
054    private static final URLTemplate FILE_URL_TEMPLATE = new URLTemplate("files/%s");
055    private static final URLTemplate CONTENT_URL_TEMPLATE = new URLTemplate("files/%s/content");
056    private static final URLTemplate VERSIONS_URL_TEMPLATE = new URLTemplate("files/%s/versions");
057    private static final URLTemplate COPY_URL_TEMPLATE = new URLTemplate("files/%s/copy");
058    private static final URLTemplate ADD_COMMENT_URL_TEMPLATE = new URLTemplate("comments");
059    private static final URLTemplate GET_COMMENTS_URL_TEMPLATE = new URLTemplate("files/%s/comments");
060    private static final URLTemplate METADATA_URL_TEMPLATE = new URLTemplate("files/%s/metadata/%s");
061    private static final URLTemplate ADD_TASK_URL_TEMPLATE = new URLTemplate("tasks");
062    private static final URLTemplate GET_TASKS_URL_TEMPLATE = new URLTemplate("files/%s/tasks");
063    private static final URLTemplate GET_THUMBNAIL_PNG_TEMPLATE = new URLTemplate("files/%s/thumbnail.png");
064    private static final URLTemplate GET_THUMBNAIL_JPG_TEMPLATE = new URLTemplate("files/%s/thumbnail.jpg");
065    private static final String DEFAULT_METADATA_TYPE = "properties";
066    private static final int BUFFER_SIZE = 8192;
067
068
069    /**
070     * Constructs a BoxFile for a file with a given ID.
071     * @param  api the API connection to be used by the file.
072     * @param  id  the ID of the file.
073     */
074    public BoxFile(BoxAPIConnection api, String id) {
075        super(api, id);
076    }
077
078    @Override
079    public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate,
080        BoxSharedLink.Permissions permissions) {
081
082        BoxSharedLink sharedLink = new BoxSharedLink(access, unshareDate, permissions);
083        Info info = new Info();
084        info.setSharedLink(sharedLink);
085
086        this.updateInfo(info);
087        return info.getSharedLink();
088    }
089
090    /**
091     * Adds a comment to this file. The message can contain @mentions by using the string @[userid:username] anywhere
092     * within the message, where userid and username are the ID and username of the person being mentioned.
093     * @see    <a href="https://developers.box.com/docs/#comments-add-a-comment-to-an-item">the tagged_message field
094     *         for including @mentions.</a>
095     * @param  message the comment's message.
096     * @return information about the newly added comment.
097     */
098    public BoxComment.Info addComment(String message) {
099        JsonObject itemJSON = new JsonObject();
100        itemJSON.add("type", "file");
101        itemJSON.add("id", this.getID());
102
103        JsonObject requestJSON = new JsonObject();
104        requestJSON.add("item", itemJSON);
105        if (BoxComment.messageContainsMention(message)) {
106            requestJSON.add("tagged_message", message);
107        } else {
108            requestJSON.add("message", message);
109        }
110
111        URL url = ADD_COMMENT_URL_TEMPLATE.build(this.getAPI().getBaseURL());
112        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
113        request.setBody(requestJSON.toString());
114        BoxJSONResponse response = (BoxJSONResponse) request.send();
115        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
116
117        BoxComment addedComment = new BoxComment(this.getAPI(), responseJSON.get("id").asString());
118        return addedComment.new Info(responseJSON);
119    }
120
121    /**
122     * Adds a new task to this file. The task can have an optional message to include, and a due date.
123     * @param action the action the task assignee will be prompted to do.
124     * @param message an optional message to include with the task.
125     * @param dueAt the day at which this task is due.
126     * @return information about the newly added task.
127     */
128    public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt) {
129        JsonObject itemJSON = new JsonObject();
130        itemJSON.add("type", "file");
131        itemJSON.add("id", this.getID());
132
133        JsonObject requestJSON = new JsonObject();
134        requestJSON.add("item", itemJSON);
135        requestJSON.add("action", action.toJSONString());
136
137        if (message != null && !message.isEmpty()) {
138            requestJSON.add("message", message);
139        }
140
141        if (dueAt != null) {
142            requestJSON.add("due_at", dueAt.toString());
143        }
144
145        URL url = ADD_TASK_URL_TEMPLATE.build(this.getAPI().getBaseURL());
146        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
147        request.setBody(requestJSON.toString());
148        BoxJSONResponse response = (BoxJSONResponse) request.send();
149        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
150
151        BoxTask addedTask = new BoxTask(this.getAPI(), responseJSON.get("id").asString());
152        return addedTask.new Info(responseJSON);
153    }
154
155    /**
156     * Gets an expiring URL for downloading a file directly from Box. This can be user,
157     * for example, for sending as a redirect to a browser to cause the browser
158     * to download the file directly from Box.
159     * @return the temporary download URL
160     */
161    public URL getDownloadURL() {
162        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
163        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
164        request.setFollowRedirects(false);
165
166        BoxRedirectResponse response = (BoxRedirectResponse) request.send();
167
168        return response.getRedirectURL();
169    }
170
171    /**
172     * Downloads the contents of this file to a given OutputStream.
173     * @param output the stream to where the file will be written.
174     */
175    public void download(OutputStream output) {
176        this.download(output, null);
177    }
178
179    /**
180     * Downloads the contents of this file to a given OutputStream while reporting the progress to a ProgressListener.
181     * @param output   the stream to where the file will be written.
182     * @param listener a listener for monitoring the download's progress.
183     */
184    public void download(OutputStream output, ProgressListener listener) {
185        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
186        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
187        BoxAPIResponse response = request.send();
188        InputStream input = response.getBody(listener);
189
190        byte[] buffer = new byte[BUFFER_SIZE];
191        try {
192            int n = input.read(buffer);
193            while (n != -1) {
194                output.write(buffer, 0, n);
195                n = input.read(buffer);
196            }
197        } catch (IOException e) {
198            throw new BoxAPIException("Couldn't connect to the Box API due to a network error.", e);
199        } finally {
200            response.disconnect();
201        }
202    }
203
204    /**
205     * Downloads a part of this file's contents, starting at specified byte offset.
206     * @param output the stream to where the file will be written.
207     * @param offset the byte offset at which to start the download.
208     */
209    public void downloadRange(OutputStream output, long offset) {
210        this.downloadRange(output, offset, -1);
211    }
212
213    /**
214     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd.
215     * @param output     the stream to where the file will be written.
216     * @param rangeStart the byte offset at which to start the download.
217     * @param rangeEnd   the byte offset at which to stop the download.
218     */
219    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd) {
220        this.downloadRange(output, rangeStart, rangeEnd, null);
221    }
222
223    /**
224     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd, while reporting the
225     * progress to a ProgressListener.
226     * @param output     the stream to where the file will be written.
227     * @param rangeStart the byte offset at which to start the download.
228     * @param rangeEnd   the byte offset at which to stop the download.
229     * @param listener   a listener for monitoring the download's progress.
230     */
231    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd, ProgressListener listener) {
232        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
233        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
234        if (rangeEnd > 0) {
235            request.addHeader("Range", String.format("bytes=%s-%s", Long.toString(rangeStart),
236                Long.toString(rangeEnd)));
237        } else {
238            request.addHeader("Range", String.format("bytes=%s-", Long.toString(rangeStart)));
239        }
240
241        BoxAPIResponse response = request.send();
242        InputStream input = response.getBody(listener);
243
244        byte[] buffer = new byte[BUFFER_SIZE];
245        try {
246            int n = input.read(buffer);
247            while (n != -1) {
248                output.write(buffer, 0, n);
249                n = input.read(buffer);
250            }
251        } catch (IOException e) {
252            throw new BoxAPIException("Couldn't connect to the Box API due to a network error.", e);
253        } finally {
254            response.disconnect();
255        }
256    }
257
258    @Override
259    public BoxFile.Info copy(BoxFolder destination) {
260        return this.copy(destination, null);
261    }
262
263    @Override
264    public BoxFile.Info copy(BoxFolder destination, String newName) {
265        URL url = COPY_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
266
267        JsonObject parent = new JsonObject();
268        parent.add("id", destination.getID());
269
270        JsonObject copyInfo = new JsonObject();
271        copyInfo.add("parent", parent);
272        if (newName != null) {
273            copyInfo.add("name", newName);
274        }
275
276        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
277        request.setBody(copyInfo.toString());
278        BoxJSONResponse response = (BoxJSONResponse) request.send();
279        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
280        BoxFile copiedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
281        return copiedFile.new Info(responseJSON);
282    }
283
284    /**
285     * Deletes this file by moving it to the trash.
286     */
287    public void delete() {
288        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
289        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
290        BoxAPIResponse response = request.send();
291        response.disconnect();
292    }
293
294    @Override
295    public BoxItem.Info move(BoxFolder destination) {
296        return this.move(destination, null);
297    }
298
299    @Override
300    public BoxItem.Info move(BoxFolder destination, String newName) {
301        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
302        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
303
304        JsonObject parent = new JsonObject();
305        parent.add("id", destination.getID());
306
307        JsonObject updateInfo = new JsonObject();
308        updateInfo.add("parent", parent);
309        if (newName != null) {
310            updateInfo.add("name", newName);
311        }
312
313        request.setBody(updateInfo.toString());
314        BoxJSONResponse response = (BoxJSONResponse) request.send();
315        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
316        BoxFile movedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
317        return movedFile.new Info(responseJSON);
318    }
319
320    /**
321     * Renames this file.
322     * @param newName the new name of the file.
323     */
324    public void rename(String newName) {
325        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
326        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
327
328        JsonObject updateInfo = new JsonObject();
329        updateInfo.add("name", newName);
330
331        request.setBody(updateInfo.toString());
332        BoxAPIResponse response = request.send();
333        response.disconnect();
334    }
335
336    @Override
337    public BoxFile.Info getInfo() {
338        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
339        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
340        BoxJSONResponse response = (BoxJSONResponse) request.send();
341        return new Info(response.getJSON());
342    }
343
344    @Override
345    public BoxFile.Info getInfo(String... fields) {
346        String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
347        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
348
349        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
350        BoxJSONResponse response = (BoxJSONResponse) request.send();
351        return new Info(response.getJSON());
352    }
353
354    /**
355     * Updates the information about this file with any info fields that have been modified locally.
356     *
357     * <p>The only fields that will be updated are the ones that have been modified locally. For example, the following
358     * code won't update any information (or even send a network request) since none of the info's fields were
359     * changed:</p>
360     *
361     * <pre>BoxFile file = new File(api, id);
362     *BoxFile.Info info = file.getInfo();
363     *file.updateInfo(info);</pre>
364     *
365     * @param info the updated info.
366     */
367    public void updateInfo(BoxFile.Info info) {
368        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
369        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
370        request.setBody(info.getPendingChanges());
371        BoxJSONResponse response = (BoxJSONResponse) request.send();
372        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
373        info.update(jsonObject);
374    }
375
376    /**
377     * Gets any previous versions of this file. Note that only users with premium accounts will be able to retrieve
378     * previous versions of their files.
379     * @return a list of previous file versions.
380     */
381    public Collection<BoxFileVersion> getVersions() {
382        URL url = VERSIONS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
383        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
384        BoxJSONResponse response = (BoxJSONResponse) request.send();
385
386        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
387        JsonArray entries = jsonObject.get("entries").asArray();
388        Collection<BoxFileVersion> versions = new ArrayList<BoxFileVersion>();
389        for (JsonValue entry : entries) {
390            versions.add(new BoxFileVersion(this.getAPI(), entry.asObject(), this.getID()));
391        }
392
393        return versions;
394    }
395
396    /**
397     * Checks if the file can be successfully uploaded by using the preflight check.
398     * @param  name        the name to give the uploaded file or null to use existing name.
399     * @param  fileSize    the size of the file used for account capacity calculations.
400     * @param  parentID    the ID of the parent folder that the new version is being uploaded to.
401     */
402    public void canUploadVersion(String name, long fileSize, String parentID) {
403        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
404        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
405
406        JsonObject parent = new JsonObject();
407        parent.add("id", parentID);
408
409        JsonObject preflightInfo = new JsonObject();
410        preflightInfo.add("parent", parent);
411        if (name != null) {
412            preflightInfo.add("name", name);
413        }
414
415        preflightInfo.add("size", fileSize);
416
417        request.setBody(preflightInfo.toString());
418        BoxAPIResponse response = request.send();
419        response.disconnect();
420    }
421
422    /**
423     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
424     * will be able to view and recover previous versions of the file.
425     * @param fileContent a stream containing the new file contents.
426     */
427    public void uploadVersion(InputStream fileContent) {
428        this.uploadVersion(fileContent, null);
429    }
430
431    /**
432     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
433     * will be able to view and recover previous versions of the file.
434     * @param fileContent a stream containing the new file contents.
435     * @param modified    the date that the new version was modified.
436     */
437    public void uploadVersion(InputStream fileContent, Date modified) {
438        this.uploadVersion(fileContent, modified, 0, null);
439    }
440
441    /**
442     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
443     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
444     * of the file.
445     * @param fileContent a stream containing the new file contents.
446     * @param modified    the date that the new version was modified.
447     * @param fileSize    the size of the file used for determining the progress of the upload.
448     * @param listener    a listener for monitoring the upload's progress.
449     */
450    public void uploadVersion(InputStream fileContent, Date modified, long fileSize, ProgressListener listener) {
451        URL uploadURL = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
452        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
453        if (fileSize > 0) {
454            request.setFile(fileContent, "", fileSize);
455        } else {
456            request.setFile(fileContent, "");
457        }
458
459        if (modified != null) {
460            request.putField("content_modified_at", modified);
461        }
462
463        BoxAPIResponse response;
464        if (listener == null) {
465            response = request.send();
466        } else {
467            response = request.send(listener);
468        }
469        response.disconnect();
470    }
471
472    /**
473     * Gets an expiring URL for creating an embedded preview session. The URL will expire after 60 seconds and the
474     * preview session will expire after 60 minutes.
475     * @return the expiring preview link
476     */
477    public URL getPreviewLink() {
478        BoxFile.Info info = this.getInfo("expiring_embed_link");
479
480        return info.getPreviewLink();
481    }
482
483
484    /**
485     * Retrieves a thumbnail, or smaller image representation, of this file. Sizes of 32x32, 64x64, 128x128,
486     * and 256x256 can be returned in the .png format and sizes of 32x32, 94x94, 160x160, and 320x320 can be returned
487     * in the .jpg format.
488     * @param fileType      either PNG of JPG
489     * @param minWidth      minimum width
490     * @param minHeight     minimum height
491     * @param maxWidth      maximum width
492     * @param maxHeight     maximum height
493     * @return the byte array of the thumbnail image
494     */
495    public byte[] getThumbnail(ThumbnailFileType fileType, int minWidth, int minHeight, int maxWidth, int maxHeight) {
496        QueryStringBuilder builder = new QueryStringBuilder();
497        builder.appendParam("min_width", minWidth);
498        builder.appendParam("min_height", minHeight);
499        builder.appendParam("max_width", maxWidth);
500        builder.appendParam("max_height", maxHeight);
501
502        URLTemplate template;
503        if (fileType == ThumbnailFileType.PNG) {
504            template = GET_THUMBNAIL_PNG_TEMPLATE;
505        } else if (fileType == ThumbnailFileType.JPG) {
506            template = GET_THUMBNAIL_JPG_TEMPLATE;
507        } else {
508            throw new BoxAPIException("Unsupported thumbnail file type");
509        }
510        URL url = template.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID());
511
512        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
513        BoxAPIResponse response = request.send();
514
515        ByteArrayOutputStream thumbOut = new ByteArrayOutputStream();
516        InputStream body = response.getBody();
517        byte[] buffer = new byte[BUFFER_SIZE];
518        try {
519            int n = body.read(buffer);
520            while (n != -1) {
521                thumbOut.write(buffer, 0, n);
522                n = body.read(buffer);
523            }
524        } catch (IOException e) {
525            throw new BoxAPIException("Error reading thumbnail bytes from response body", e);
526        } finally {
527            response.disconnect();
528        }
529
530        return thumbOut.toByteArray();
531    }
532
533    /**
534     * Gets a list of any comments on this file.
535     * @return a list of comments on this file.
536     */
537    public List<BoxComment.Info> getComments() {
538        URL url = GET_COMMENTS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
539        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
540        BoxJSONResponse response = (BoxJSONResponse) request.send();
541        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
542
543        int totalCount = responseJSON.get("total_count").asInt();
544        List<BoxComment.Info> comments = new ArrayList<BoxComment.Info>(totalCount);
545        JsonArray entries = responseJSON.get("entries").asArray();
546        for (JsonValue value : entries) {
547            JsonObject commentJSON = value.asObject();
548            BoxComment comment = new BoxComment(this.getAPI(), commentJSON.get("id").asString());
549            BoxComment.Info info = comment.new Info(commentJSON);
550            comments.add(info);
551        }
552
553        return comments;
554    }
555
556    /**
557     * Gets a list of any tasks on this file.
558     * @return a list of tasks on this file.
559     */
560    public List<BoxTask.Info> getTasks() {
561        URL url = GET_TASKS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
562        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
563        BoxJSONResponse response = (BoxJSONResponse) request.send();
564        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
565
566        int totalCount = responseJSON.get("total_count").asInt();
567        List<BoxTask.Info> tasks = new ArrayList<BoxTask.Info>(totalCount);
568        JsonArray entries = responseJSON.get("entries").asArray();
569        for (JsonValue value : entries) {
570            JsonObject taskJSON = value.asObject();
571            BoxTask task = new BoxTask(this.getAPI(), taskJSON.get("id").asString());
572            BoxTask.Info info = task.new Info(taskJSON);
573            tasks.add(info);
574        }
575
576        return tasks;
577    }
578
579    /**
580     * Creates metadata on this file.
581     * @param metadata The new metadata values.
582     * @return the metadata returned from the server.
583     */
584    public Metadata createMetadata(Metadata metadata) {
585        return this.createMetadata(DEFAULT_METADATA_TYPE, metadata);
586    }
587
588    /**
589     * Creates the metadata of specified type.
590     * @param typeName the metadata type name.
591     * @param metadata the new metadata values.
592     * @return the metadata returned from the server.
593     */
594    public Metadata createMetadata(String typeName, Metadata metadata) {
595        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), typeName);
596        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "POST");
597        request.addHeader("Content-Type", "application/json");
598        request.setBody(metadata.toString());
599        BoxJSONResponse response = (BoxJSONResponse) request.send();
600        return new Metadata(JsonObject.readFrom(response.getJSON()));
601    }
602
603    /**
604     * Gets the file properties metadata.
605     * @return the metadata returned from the server.
606     */
607    public Metadata getMetadata() {
608        return this.getMetadata(DEFAULT_METADATA_TYPE);
609    }
610
611    /**
612     * Gets the file metadata of specified type.
613     * @param typeName the metadata type name.
614     * @return the metadata returned from the server.
615     */
616    public Metadata getMetadata(String typeName) {
617        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), typeName);
618        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
619        BoxJSONResponse response = (BoxJSONResponse) request.send();
620        return new Metadata(JsonObject.readFrom(response.getJSON()));
621    }
622
623    /**
624     * Updates the file metadata.
625     * @param metadata the new metadata values.
626     * @return the metadata returned from the server.
627     */
628    public Metadata updateMetadata(Metadata metadata) {
629        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), metadata.getTypeName());
630        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
631        request.addHeader("Content-Type", "application/json-patch+json");
632        request.setBody(metadata.getPatch());
633        BoxJSONResponse response = (BoxJSONResponse) request.send();
634        return new Metadata(JsonObject.readFrom(response.getJSON()));
635    }
636
637    /**
638     * Deletes the file properties metadata.
639     */
640    public void deleteMetadata() {
641        this.deleteMetadata(DEFAULT_METADATA_TYPE);
642    }
643
644    /**
645     * Deletes the file metadata of specified type.
646     * @param typeName the metadata type name.
647     */
648    public void deleteMetadata(String typeName) {
649        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), typeName);
650        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
651        request.send();
652    }
653
654    /**
655     * Contains information about a BoxFile.
656     */
657    public class Info extends BoxItem.Info {
658        private String sha1;
659        private String versionNumber;
660        private long commentCount;
661        private EnumSet<Permission> permissions;
662        private String extension;
663        private boolean isPackage;
664        private BoxFileVersion version;
665        private URL previewLink;
666
667        /**
668         * Constructs an empty Info object.
669         */
670        public Info() {
671            super();
672        }
673
674        /**
675         * Constructs an Info object by parsing information from a JSON string.
676         * @param  json the JSON string to parse.
677         */
678        public Info(String json) {
679            super(json);
680        }
681
682        /**
683         * Constructs an Info object using an already parsed JSON object.
684         * @param  jsonObject the parsed JSON object.
685         */
686        Info(JsonObject jsonObject) {
687            super(jsonObject);
688        }
689
690        @Override
691        public BoxFile getResource() {
692            return BoxFile.this;
693        }
694
695        /**
696         * Gets the SHA1 hash of the file.
697         * @return the SHA1 hash of the file.
698         */
699        public String getSha1() {
700            return this.sha1;
701        }
702
703        /**
704         * Gets the current version number of the file.
705         * @return the current version number of the file.
706         */
707        public String getVersionNumber() {
708            return this.versionNumber;
709        }
710
711        /**
712         * Gets the number of comments on the file.
713         * @return the number of comments on the file.
714         */
715        public long getCommentCount() {
716            return this.commentCount;
717        }
718
719        /**
720         * Gets the permissions that the current user has on the file.
721         * @return the permissions that the current user has on the file.
722         */
723        public EnumSet<Permission> getPermissions() {
724            return this.permissions;
725        }
726
727        /**
728         * Gets the extension suffix of the file, excluding the dot.
729         * @return the extension of the file.
730         */
731        public String getExtension() {
732            return this.extension;
733        }
734
735        /**
736         * Gets whether or not the file is an OSX package.
737         * @return true if the file is an OSX package; otherwise false.
738         */
739        public boolean getIsPackage() {
740            return this.isPackage;
741        }
742
743        /**
744         * Gets the current version details of the file.
745         * @return the current version details of the file.
746         */
747        public BoxFileVersion getVersion() {
748            return this.version;
749        }
750
751        /**
752         * Gets the current expiring preview link.
753         * @return the expiring preview link
754         */
755        public URL getPreviewLink() {
756            return this.previewLink;
757        }
758
759        @Override
760        protected void parseJSONMember(JsonObject.Member member) {
761            super.parseJSONMember(member);
762
763            String memberName = member.getName();
764            JsonValue value = member.getValue();
765            if (memberName.equals("sha1")) {
766                this.sha1 = value.asString();
767            } else if (memberName.equals("version_number")) {
768                this.versionNumber = value.asString();
769            } else if (memberName.equals("comment_count")) {
770                this.commentCount = value.asLong();
771            } else if (memberName.equals("permissions")) {
772                this.permissions = this.parsePermissions(value.asObject());
773            } else if (memberName.equals("extension")) {
774                this.extension = value.asString();
775            } else if (memberName.equals("is_package")) {
776                this.isPackage = value.asBoolean();
777            } else if (memberName.equals("file_version")) {
778                this.version = this.parseFileVersion(value.asObject());
779            } else if (memberName.equals("expiring_embed_link")) {
780                try {
781                    String urlString = member.getValue().asObject().get("url").asString();
782                    this.previewLink = new URL(urlString);
783                } catch (MalformedURLException e) {
784                    throw new BoxAPIException("Couldn't parse expiring_embed_link/url for file", e);
785                }
786            }
787        }
788
789        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
790            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
791            for (JsonObject.Member member : jsonObject) {
792                JsonValue value = member.getValue();
793                if (value.isNull() || !value.asBoolean()) {
794                    continue;
795                }
796
797                String memberName = member.getName();
798                if (memberName.equals("can_download")) {
799                    permissions.add(Permission.CAN_DOWNLOAD);
800                } else if (memberName.equals("can_upload")) {
801                    permissions.add(Permission.CAN_UPLOAD);
802                } else if (memberName.equals("can_rename")) {
803                    permissions.add(Permission.CAN_RENAME);
804                } else if (memberName.equals("can_delete")) {
805                    permissions.add(Permission.CAN_DELETE);
806                } else if (memberName.equals("can_share")) {
807                    permissions.add(Permission.CAN_SHARE);
808                } else if (memberName.equals("can_set_share_access")) {
809                    permissions.add(Permission.CAN_SET_SHARE_ACCESS);
810                } else if (memberName.equals("can_preview")) {
811                    permissions.add(Permission.CAN_PREVIEW);
812                } else if (memberName.equals("can_comment")) {
813                    permissions.add(Permission.CAN_COMMENT);
814                }
815            }
816
817            return permissions;
818        }
819
820        private BoxFileVersion parseFileVersion(JsonObject jsonObject) {
821            return new BoxFileVersion(BoxFile.this.getAPI(), jsonObject, BoxFile.this.getID());
822        }
823    }
824
825    /**
826     * Enumerates the possible permissions that a user can have on a file.
827     */
828    public enum Permission {
829        /**
830         * The user can download the file.
831         */
832        CAN_DOWNLOAD ("can_download"),
833
834        /**
835         * The user can upload new versions of the file.
836         */
837        CAN_UPLOAD ("can_upload"),
838
839        /**
840         * The user can rename the file.
841         */
842        CAN_RENAME ("can_rename"),
843
844        /**
845         * The user can delete the file.
846         */
847        CAN_DELETE ("can_delete"),
848
849        /**
850         * The user can share the file.
851         */
852        CAN_SHARE ("can_share"),
853
854        /**
855         * The user can set the access level for shared links to the file.
856         */
857        CAN_SET_SHARE_ACCESS ("can_set_share_access"),
858
859        /**
860         * The user can preview the file.
861         */
862        CAN_PREVIEW ("can_preview"),
863
864        /**
865         * The user can comment on the file.
866         */
867        CAN_COMMENT ("can_comment");
868
869        private final String jsonValue;
870
871        private Permission(String jsonValue) {
872            this.jsonValue = jsonValue;
873        }
874
875        static Permission fromJSONValue(String jsonValue) {
876            return Permission.valueOf(jsonValue.toUpperCase());
877        }
878
879        String toJSONValue() {
880            return this.jsonValue;
881        }
882    }
883}