• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

box / box-java-sdk / #4292

16 Dec 2024 09:34AM UTC coverage: 71.866% (+0.1%) from 71.724%
#4292

push

github

web-flow
feat: Support downloading file from shared link (#1282)


---------

Co-authored-by: Minh Nguyen Cong <mcong@box.com>

19 of 23 new or added lines in 2 files covered. (82.61%)

1 existing line in 1 file now uncovered.

8105 of 11278 relevant lines covered (71.87%)

0.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.14
/src/main/java/com/box/sdk/BoxFile.java
1
package com.box.sdk;
2

3
import static com.box.sdk.BinaryBodyUtils.writeStream;
4
import static com.box.sdk.BinaryBodyUtils.writeStreamWithContentLength;
5
import static com.box.sdk.http.ContentType.APPLICATION_JSON;
6
import static com.box.sdk.http.ContentType.APPLICATION_JSON_PATCH;
7
import static com.eclipsesource.json.Json.NULL;
8

9
import com.box.sdk.http.HttpMethod;
10
import com.box.sdk.internal.utils.Parsers;
11
import com.box.sdk.sharedlink.BoxSharedLinkRequest;
12
import com.eclipsesource.json.Json;
13
import com.eclipsesource.json.JsonArray;
14
import com.eclipsesource.json.JsonObject;
15
import com.eclipsesource.json.JsonValue;
16
import java.io.IOException;
17
import java.io.InputStream;
18
import java.io.OutputStream;
19
import java.net.MalformedURLException;
20
import java.net.URL;
21
import java.util.ArrayList;
22
import java.util.Arrays;
23
import java.util.Collection;
24
import java.util.Date;
25
import java.util.EnumSet;
26
import java.util.HashSet;
27
import java.util.List;
28
import java.util.Map;
29
import java.util.Optional;
30
import java.util.Set;
31
import java.util.concurrent.TimeUnit;
32

33

34
/**
35
 * Represents an individual file on Box. This class can be used to download a file's contents, upload new versions, and
36
 * perform other common file operations (move, copy, delete, etc.).
37
 *
38
 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked
39
 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error
40
 * handling for errors related to the Box REST API, you should capture this exception explicitly.
41
 */
42
@BoxResourceType("file")
43
public class BoxFile extends BoxItem {
44

45
    /**
46
     * An array of all possible file fields that can be requested when calling {@link #getInfo(String...)}.
47
     */
48
    public static final String[] ALL_FIELDS = {"type", "id", "sequence_id", "etag", "sha1", "name",
1✔
49
        "description", "size", "path_collection", "created_at", "modified_at",
50
        "trashed_at", "purged_at", "content_created_at", "content_modified_at",
51
        "created_by", "modified_by", "owned_by", "shared_link", "parent",
52
        "item_status", "version_number", "comment_count", "permissions", "tags",
53
        "lock", "extension", "is_package", "file_version", "collections",
54
        "watermark_info", "metadata", "representations",
55
        "is_external_only", "expiring_embed_link", "allowed_invitee_roles",
56
        "has_collaborations", "disposition_at", "is_accessible_via_shared_link"};
57

58
    /**
59
     * An array of all possible version fields that can be requested when calling {@link #getVersions(String...)}.
60
     */
61
    public static final String[] ALL_VERSION_FIELDS = {"id", "sha1", "name", "size", "uploader_display_name",
1✔
62
        "created_at", "modified_at", "modified_by", "trashed_at", "trashed_by", "restored_at", "restored_by",
63
        "purged_at", "file_version", "version_number"};
64
    /**
65
     * File URL Template.
66
     */
67
    public static final URLTemplate FILE_URL_TEMPLATE = new URLTemplate("files/%s");
1✔
68
    /**
69
     * Content URL Template.
70
     */
71
    public static final URLTemplate CONTENT_URL_TEMPLATE = new URLTemplate("files/%s/content");
1✔
72
    /**
73
     * Versions URL Template.
74
     */
75
    public static final URLTemplate VERSIONS_URL_TEMPLATE = new URLTemplate("files/%s/versions");
1✔
76
    /**
77
     * Copy URL Template.
78
     */
79
    public static final URLTemplate COPY_URL_TEMPLATE = new URLTemplate("files/%s/copy");
1✔
80
    /**
81
     * Add Comment URL Template.
82
     */
83
    public static final URLTemplate ADD_COMMENT_URL_TEMPLATE = new URLTemplate("comments");
1✔
84
    /**
85
     * Get Comments URL Template.
86
     */
87
    public static final URLTemplate GET_COMMENTS_URL_TEMPLATE = new URLTemplate("files/%s/comments");
1✔
88
    /**
89
     * Metadata URL Template.
90
     */
91
    public static final URLTemplate METADATA_URL_TEMPLATE = new URLTemplate("files/%s/metadata/%s/%s");
1✔
92
    /**
93
     * Add Task URL Template.
94
     */
95
    public static final URLTemplate ADD_TASK_URL_TEMPLATE = new URLTemplate("tasks");
1✔
96
    /**
97
     * Get Tasks URL Template.
98
     */
99
    public static final URLTemplate GET_TASKS_URL_TEMPLATE = new URLTemplate("files/%s/tasks");
1✔
100
    /**
101
     * Get Thumbnail PNG Template.
102
     */
103
    public static final URLTemplate GET_THUMBNAIL_PNG_TEMPLATE = new URLTemplate("files/%s/thumbnail.png");
1✔
104
    /**
105
     * Get Thumbnail JPG Template.
106
     */
107
    public static final URLTemplate GET_THUMBNAIL_JPG_TEMPLATE = new URLTemplate("files/%s/thumbnail.jpg");
1✔
108
    /**
109
     * Upload Session URL Template.
110
     */
111
    public static final URLTemplate UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/%s/upload_sessions");
1✔
112
    /**
113
     * Upload Session Status URL Template.
114
     */
115
    public static final URLTemplate UPLOAD_SESSION_STATUS_URL_TEMPLATE = new URLTemplate(
1✔
116
        "files/upload_sessions/%s/status");
117
    /**
118
     * Abort Upload Session URL Template.
119
     */
120
    public static final URLTemplate ABORT_UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/upload_sessions/%s");
1✔
121
    /**
122
     * Add Collaborations URL Template.
123
     */
124
    public static final URLTemplate ADD_COLLABORATION_URL = new URLTemplate("collaborations");
1✔
125
    /**
126
     * Get All File Collaborations URL Template.
127
     */
128
    public static final URLTemplate GET_ALL_FILE_COLLABORATIONS_URL = new URLTemplate("files/%s/collaborations");
1✔
129
    /**
130
     * Describes file item type.
131
     */
132
    static final String TYPE = "file";
133
    private static final int GET_COLLABORATORS_PAGE_SIZE = 1000;
134

135
    /**
136
     * Constructs a BoxFile for a file with a given ID.
137
     *
138
     * @param api the API connection to be used by the file.
139
     * @param id  the ID of the file.
140
     */
141
    public BoxFile(BoxAPIConnection api, String id) {
142
        super(api, id);
1✔
143
    }
1✔
144

145
    /**
146
     * {@inheritDoc}
147
     */
148
    @Override
149
    protected URL getItemURL() {
150
        return FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
×
151
    }
152

153
    /**
154
     * Creates a shared link.
155
     *
156
     * @param sharedLinkRequest Shared link to create
157
     * @return Created shared link.
158
     */
159
    public BoxSharedLink createSharedLink(BoxSharedLinkRequest sharedLinkRequest) {
160
        return createSharedLink(sharedLinkRequest.asSharedLink());
1✔
161
    }
162

163
    private BoxSharedLink createSharedLink(BoxSharedLink sharedLink) {
164
        Info info = new Info();
1✔
165
        info.setSharedLink(sharedLink);
1✔
166

167
        this.updateInfo(info);
1✔
168
        return info.getSharedLink();
1✔
169
    }
170

171
    /**
172
     * Adds new {@link BoxWebHook} to this {@link BoxFile}.
173
     *
174
     * @param address  {@link BoxWebHook.Info#getAddress()}
175
     * @param triggers {@link BoxWebHook.Info#getTriggers()}
176
     * @return created {@link BoxWebHook.Info}
177
     */
178
    public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) {
179
        return BoxWebHook.create(this, address, triggers);
×
180
    }
181

182
    /**
183
     * Adds a comment to this file. The message can contain @mentions by using the string @[userid:username] anywhere
184
     * within the message, where userid and username are the ID and username of the person being mentioned.
185
     *
186
     * @param message the comment's message.
187
     * @return information about the newly added comment.
188
     * @see <a href="https://developers.box.com/docs/#comments-add-a-comment-to-an-item">the tagged_message field
189
     * for including @mentions.</a>
190
     */
191
    public BoxComment.Info addComment(String message) {
192
        JsonObject itemJSON = new JsonObject();
1✔
193
        itemJSON.add("type", "file");
1✔
194
        itemJSON.add("id", this.getID());
1✔
195

196
        JsonObject requestJSON = new JsonObject();
1✔
197
        requestJSON.add("item", itemJSON);
1✔
198
        if (BoxComment.messageContainsMention(message)) {
1✔
199
            requestJSON.add("tagged_message", message);
×
200
        } else {
201
            requestJSON.add("message", message);
1✔
202
        }
203

204
        URL url = ADD_COMMENT_URL_TEMPLATE.build(this.getAPI().getBaseURL());
1✔
205
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1✔
206
        request.setBody(requestJSON.toString());
1✔
207
        try (BoxJSONResponse response = request.send()) {
1✔
208
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1✔
209

210
            BoxComment addedComment = new BoxComment(this.getAPI(), responseJSON.get("id").asString());
1✔
211
            return addedComment.new Info(responseJSON);
1✔
212
        }
213
    }
214

215
    /**
216
     * Adds a new task to this file. The task can have an optional message to include, and a due date.
217
     *
218
     * @param action  the action the task assignee will be prompted to do.
219
     * @param message an optional message to include with the task.
220
     * @param dueAt   the day at which this task is due.
221
     * @return information about the newly added task.
222
     */
223
    public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt) {
224
        return this.addTask(action, message, dueAt, null);
×
225
    }
226

227
    /**
228
     * Adds a new task to this file. The task can have an optional message to include, due date,
229
     * and task completion rule.
230
     *
231
     * @param action         the action the task assignee will be prompted to do.
232
     * @param message        an optional message to include with the task.
233
     * @param dueAt          the day at which this task is due.
234
     * @param completionRule the rule for completing the task.
235
     * @return information about the newly added task.
236
     */
237
    public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt,
238
                                BoxTask.CompletionRule completionRule) {
239
        JsonObject itemJSON = new JsonObject();
1✔
240
        itemJSON.add("type", "file");
1✔
241
        itemJSON.add("id", this.getID());
1✔
242

243
        JsonObject requestJSON = new JsonObject();
1✔
244
        requestJSON.add("item", itemJSON);
1✔
245
        requestJSON.add("action", action.toJSONString());
1✔
246

247
        if (message != null && !message.isEmpty()) {
1✔
248
            requestJSON.add("message", message);
1✔
249
        }
250

251
        if (dueAt != null) {
1✔
252
            requestJSON.add("due_at", BoxDateFormat.format(dueAt));
1✔
253
        }
254

255
        if (completionRule != null) {
1✔
256
            requestJSON.add("completion_rule", completionRule.toJSONString());
1✔
257
        }
258

259
        URL url = ADD_TASK_URL_TEMPLATE.build(this.getAPI().getBaseURL());
1✔
260
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1✔
261
        request.setBody(requestJSON.toString());
1✔
262
        try (BoxJSONResponse response = request.send()) {
1✔
263
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1✔
264

265
            BoxTask addedTask = new BoxTask(this.getAPI(), responseJSON.get("id").asString());
1✔
266
            return addedTask.new Info(responseJSON);
1✔
267
        }
268
    }
269

270
    /**
271
     * Gets an expiring URL for downloading a file directly from Box. This can be user,
272
     * for example, for sending as a redirect to a browser to cause the browser
273
     * to download the file directly from Box.
274
     *
275
     * @return the temporary download URL
276
     */
277
    public URL getDownloadURL() {
278
        URL url = getDownloadUrl();
×
279
        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
×
280
        request.setFollowRedirects(false);
×
281

282
        try (BoxAPIResponse response = request.send()) {
×
283
            String location = response.getHeaderField("location");
×
284

285
            try {
286
                return new URL(location);
×
287
            } catch (MalformedURLException e) {
×
288
                throw new RuntimeException(e);
×
289
            }
290
        }
291
    }
292

293
    /**
294
     * Downloads the contents of this file to a given OutputStream.
295
     *
296
     * @param output the stream to where the file will be written.
297
     */
298
    public void download(OutputStream output) {
299
        this.download(output, null);
×
300
    }
×
301

302
    /**
303
     * Downloads the contents of this file to a given OutputStream while reporting the progress to a ProgressListener.
304
     *
305
     * @param output   the stream to where the file will be written.
306
     * @param listener a listener for monitoring the download's progress.
307
     */
308
    public void download(OutputStream output, ProgressListener listener) {
309
        URL url = getDownloadUrl();
×
310
        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
×
311
        BoxAPIResponse response = request.send();
×
312
        writeStream(response, output, listener);
×
313
    }
×
314

315
    /**
316
     * Downloads the content of the file to a given OutputStream using the provided shared link.
317
     * @param api the API connection to be used to get download URL of the file.
318
     * @param output the stream to where the file will be written.
319
     * @param sharedLink the shared link of the file.
320
     */
321
    public static void downloadFromSharedLink(BoxAPIConnection api, OutputStream output, String sharedLink) {
NEW
322
        downloadFromSharedLink(api, output, sharedLink, null, null);
×
NEW
323
    }
×
324

325
    /**
326
     * Downloads the content of the file to a given OutputStream using the provided shared link.
327
     * @param api the API connection to be used to get download URL of the file.
328
     * @param output the stream to where the file will be written.
329
     * @param sharedLink the shared link of the file.
330
     * @param password the password for the shared link.
331
     */
332
    public static void downloadFromSharedLink(
333
            BoxAPIConnection api, OutputStream output, String sharedLink, String password
334
    ) {
335
        downloadFromSharedLink(api, output, sharedLink, password, null);
1✔
336
    }
1✔
337

338
    /**
339
     * Downloads the content of the file to a given OutputStream using the provided shared link.
340
     * @param api the API connection to be used to get download URL of the file.
341
     * @param output the stream to where the file will be written.
342
     * @param sharedLink the shared link of the file.
343
     * @param listener a listener for monitoring the download's progress.
344
     */
345
    public static void downloadFromSharedLink(
346
            BoxAPIConnection api, OutputStream output, String sharedLink, ProgressListener listener
347
    ) {
348
        downloadFromSharedLink(api, output, sharedLink, null, listener);
1✔
349
    }
1✔
350

351
    /**
352
     * Downloads the content of the file to a given OutputStream using the provided shared link.
353
     * @param api the API connection to be used to get download URL of the file.
354
     * @param output the stream to where the file will be written.
355
     * @param sharedLink the shared link of the file.
356
     * @param password the password for the shared link.
357
     * @param listener a listener for monitoring the download's progress.
358
     */
359
    public static void downloadFromSharedLink(
360
            BoxAPIConnection api, OutputStream output, String sharedLink, String password, ProgressListener listener
361
    ) {
362
        BoxItem.Info item = BoxItem.getSharedItem(api, sharedLink, password, "id");
1✔
363
        if (!(item instanceof BoxFile.Info)) {
1✔
NEW
364
            throw new BoxAPIException("The shared link provided is not a shared link for a file.");
×
365
        }
366
        BoxFile sharedFile = new BoxFile(api, item.getID());
1✔
367
        URL url = sharedFile.getDownloadUrl();
1✔
368
        BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
1✔
369
        request.addHeader("BoxApi", BoxSharedLink.getSharedLinkHeaderValue(sharedLink, password));
1✔
370
        BoxAPIResponse response = request.send();
1✔
371
        writeStream(response, output, listener);
1✔
372
    }
1✔
373

374
    /**
375
     * Downloads a part of this file's contents, starting at specified byte offset.
376
     *
377
     * @param output the stream to where the file will be written.
378
     * @param offset the byte offset at which to start the download.
379
     */
380
    public void downloadRange(OutputStream output, long offset) {
381
        this.downloadRange(output, offset, -1);
×
382
    }
×
383

384
    /**
385
     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd.
386
     *
387
     * @param output     the stream to where the file will be written.
388
     * @param rangeStart the byte offset at which to start the download.
389
     * @param rangeEnd   the byte offset at which to stop the download.
390
     */
391
    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd) {
392
        this.downloadRange(output, rangeStart, rangeEnd, null);
×
393
    }
×
394

395
    /**
396
     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd, while reporting the
397
     * progress to a ProgressListener.
398
     *
399
     * @param output     the stream to where the file will be written.
400
     * @param rangeStart the byte offset at which to start the download.
401
     * @param rangeEnd   the byte offset at which to stop the download.
402
     * @param listener   a listener for monitoring the download's progress.
403
     */
404
    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd, ProgressListener listener) {
405
        URL url = getDownloadUrl();
×
406
        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
×
407
        if (rangeEnd > 0) {
×
408
            request.addHeader("Range", String.format("bytes=%s-%s", rangeStart, rangeEnd));
×
409
        } else {
410
            request.addHeader("Range", String.format("bytes=%s-", rangeStart));
×
411
        }
412
        writeStream(request.send(), output, listener);
×
413
    }
×
414

415
    /**
416
     * Can be used to override the URL used for file download.
417
     *
418
     * @return URL for file downalod
419
     */
420
    protected URL getDownloadUrl() {
421
        return CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
422
    }
423

424
    @Override
425
    public BoxFile.Info copy(BoxFolder destination) {
426
        return this.copy(destination, null);
1✔
427
    }
428

429
    @Override
430
    public BoxFile.Info copy(BoxFolder destination, String newName) {
431
        URL url = COPY_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
432

433
        JsonObject parent = new JsonObject();
1✔
434
        parent.add("id", destination.getID());
1✔
435

436
        JsonObject copyInfo = new JsonObject();
1✔
437
        copyInfo.add("parent", parent);
1✔
438
        if (newName != null) {
1✔
439
            copyInfo.add("name", newName);
×
440
        }
441

442
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1✔
443
        request.setBody(copyInfo.toString());
1✔
444
        try (BoxJSONResponse response = request.send()) {
1✔
445
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1✔
446
            BoxFile copiedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
1✔
447
            return copiedFile.new Info(responseJSON);
1✔
448
        }
449
    }
450

451
    /**
452
     * Deletes this file by moving it to the trash.
453
     */
454
    public void delete() {
455
        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
456
        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
1✔
457
        request.send().close();
1✔
458
    }
1✔
459

460
    @Override
461
    public BoxItem.Info move(BoxFolder destination) {
462
        return this.move(destination, null);
1✔
463
    }
464

465
    @Override
466
    public BoxItem.Info move(BoxFolder destination, String newName) {
467
        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
468
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
1✔
469

470
        JsonObject parent = new JsonObject();
1✔
471
        parent.add("id", destination.getID());
1✔
472

473
        JsonObject updateInfo = new JsonObject();
1✔
474
        updateInfo.add("parent", parent);
1✔
475
        if (newName != null) {
1✔
476
            updateInfo.add("name", newName);
×
477
        }
478

479
        request.setBody(updateInfo.toString());
1✔
480
        try (BoxJSONResponse response = request.send()) {
1✔
481
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1✔
482
            BoxFile movedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
1✔
483
            return movedFile.new Info(responseJSON);
1✔
484
        }
485
    }
486

487
    /**
488
     * Renames this file.
489
     *
490
     * @param newName the new name of the file.
491
     */
492
    public void rename(String newName) {
493
        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
494
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
1✔
495

496
        JsonObject updateInfo = new JsonObject();
1✔
497
        updateInfo.add("name", newName);
1✔
498

499
        request.setBody(updateInfo.toString());
1✔
500
        try (BoxJSONResponse response = request.send()) {
1✔
501
            response.getJSON();
1✔
502
        }
503
    }
1✔
504

505
    @Override
506
    public BoxFile.Info getInfo(String... fields) {
507
        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
508
        if (fields.length > 0) {
1✔
509
            String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
×
510
            url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
×
511
        }
512

513
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
514
        try (BoxJSONResponse response = request.send()) {
1✔
515
            return new Info(response.getJSON());
1✔
516
        }
517
    }
518

519
    /**
520
     * Gets information about this item including a specified set of representations.
521
     *
522
     * @param representationHints hints for representations to be retrieved
523
     * @param fields              the fields to retrieve.
524
     * @return info about this item containing only the specified fields, including representations.
525
     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
526
     */
527
    public BoxFile.Info getInfoWithRepresentations(String representationHints, String... fields) {
528
        if (representationHints.matches(Representation.X_REP_HINTS_PATTERN)) {
1✔
529
            //Since the user intends to get representations, add it to fields, even if user has missed it
530
            Set<String> fieldsSet = new HashSet<>(Arrays.asList(fields));
1✔
531
            fieldsSet.add("representations");
1✔
532
            String queryString = new QueryStringBuilder().appendParam("fields",
1✔
533
                fieldsSet.toArray(new String[0])).toString();
1✔
534
            URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1✔
535

536
            BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
537
            request.addHeader("X-Rep-Hints", representationHints);
1✔
538
            try (BoxJSONResponse response = request.send()) {
1✔
539
                return new Info(response.getJSON());
1✔
540
            }
541
        } else {
542
            throw new BoxAPIException(
×
543
                "Represention hints is not valid. Refer documention on how to construct X-Rep-Hints Header"
544
            );
545
        }
546
    }
547

548
    /**
549
     * Fetches the contents of a file representation and writes them to the provided output stream.
550
     *
551
     * @param representationHint the X-Rep-Hints query for the representation to fetch.
552
     * @param output             the output stream to write the contents to.
553
     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
554
     */
555
    public void getRepresentationContent(String representationHint, OutputStream output) {
556

557
        this.getRepresentationContent(representationHint, "", output);
1✔
558
    }
1✔
559

560
    /**
561
     * Fetches the contents of a file representation with asset path and writes them to the provided output stream.
562
     *
563
     * @param representationHint the X-Rep-Hints query for the representation to fetch.
564
     * @param assetPath          the path of the asset for representations containing multiple files.
565
     * @param output             the output stream to write the contents to.
566
     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
567
     */
568
    public void getRepresentationContent(String representationHint, String assetPath, OutputStream output) {
569
        this.getRepresentationContent(representationHint, assetPath, output, Integer.MAX_VALUE);
1✔
570
    }
1✔
571

572
    /**
573
     * Fetches the contents of a file representation with asset path and writes them to the provided output stream.
574
     *
575
     * @param representationHint the X-Rep-Hints query for the representation to fetch.
576
     * @param assetPath          the path of the asset for representations containing multiple files.
577
     * @param output             the output stream to write the contents to.
578
     * @param maxRetries         the maximum number of attempts to call the request for retrieving status information
579
     *                           indicating whether the representation has been generated and is ready to fetch.
580
     *                           If the number of attempts is exceeded, the method will throw a BoxApiException.
581
     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
582
     */
583
    public void getRepresentationContent(
584
        String representationHint, String assetPath, OutputStream output, int maxRetries
585
    ) {
586
        List<Representation> reps = this.getInfoWithRepresentations(representationHint).getRepresentations();
1✔
587
        if (reps.size() < 1) {
1✔
588
            throw new BoxAPIException("No matching representations found for requested '" + representationHint
×
589
                + "' hint");
590
        }
591
        Representation representation = reps.get(0);
1✔
592
        String repState = representation.getStatus().getState();
1✔
593

594
        switch (repState) {
1✔
595
            case "viewable":
596
            case "success":
597
                this.makeRepresentationContentRequest(representation.getContent().getUrlTemplate(), assetPath, output);
×
598
                break;
×
599
            case "pending":
600
            case "none":
601

602
                String repContentURLString = null;
1✔
603
                int attemptNumber = 0;
1✔
604
                while (repContentURLString == null && attemptNumber < maxRetries) {
1✔
605
                    repContentURLString = this.pollRepInfo(representation.getInfo().getUrl());
1✔
606
                    try {
607
                        Thread.sleep(100);
1✔
608
                    } catch (InterruptedException e) {
×
609
                        throw new RuntimeException(e);
×
610
                    }
1✔
611
                    attemptNumber++;
1✔
612
                }
613

614
                if (repContentURLString != null) {
1✔
615
                    this.makeRepresentationContentRequest(repContentURLString, assetPath, output);
1✔
616
                } else {
617
                    throw new BoxAPIException(
1✔
618
                        "Representation did not have a success status allowing it to be retrieved after "
619
                            + maxRetries
620
                            + " attempts"
621
                    );
622
                }
623

624
                break;
625
            case "error":
626
                throw new BoxAPIException("Representation had error status");
×
627
            default:
628
                throw new BoxAPIException("Representation had unknown status");
×
629
        }
630

631
    }
1✔
632

633
    private String pollRepInfo(URL infoURL) {
634

635
        BoxJSONRequest infoRequest = new BoxJSONRequest(this.getAPI(), infoURL, HttpMethod.GET);
1✔
636
        try (BoxJSONResponse infoResponse = infoRequest.send()) {
1✔
637
            JsonObject response = infoResponse.getJsonObject();
1✔
638

639
            Representation rep = new Representation(response);
1✔
640

641
            String repState = rep.getStatus().getState();
1✔
642

643
            switch (repState) {
1✔
644
                case "viewable":
645
                case "success":
646
                    return rep.getContent().getUrlTemplate();
1✔
647
                case "pending":
648
                case "none":
649
                    return null;
1✔
650
                case "error":
651
                    throw new BoxAPIException("Representation had error status");
×
652
                default:
653
                    throw new BoxAPIException("Representation had unknown status");
×
654
            }
655
        }
656
    }
657

658
    private void makeRepresentationContentRequest(
659
        String representationURLTemplate, String assetPath, OutputStream output
660
    ) {
661
        try {
662
            URL repURL = new URL(representationURLTemplate.replace("{+asset_path}", assetPath));
1✔
663
            BoxAPIRequest repContentReq = new BoxAPIRequest(this.getAPI(), repURL, HttpMethod.GET);
1✔
664
            BoxAPIResponse response = repContentReq.send();
1✔
665
            writeStreamWithContentLength(response, output);
1✔
666
        } catch (MalformedURLException ex) {
×
667

668
            throw new BoxAPIException("Could not generate representation content URL");
×
669
        }
1✔
670
    }
1✔
671

672
    /**
673
     * Updates the information about this file with any info fields that have been modified locally.
674
     *
675
     * <p>The only fields that will be updated are the ones that have been modified locally. For example, the following
676
     * code won't update any information (or even send a network request) since none of the info's fields were
677
     * changed:</p>
678
     *
679
     * <pre>BoxFile file = new File(api, id);
680
     * BoxFile.Info info = file.getInfo();
681
     * file.updateInfo(info);</pre>
682
     *
683
     * @param info the updated info.
684
     */
685
    public void updateInfo(BoxFile.Info info) {
686
        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
687
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
1✔
688
        request.setBody(info.getPendingChanges());
1✔
689
        try (BoxJSONResponse response = request.send()) {
1✔
690
            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
1✔
691
            info.update(jsonObject);
1✔
692
        }
693
    }
1✔
694

695
    /**
696
     * Retrieve a specific file version.
697
     *
698
     * @param fileVersionID the ID of the file version to retrieve.
699
     * @param fields        the optional fields to retrieve.
700
     * @return a specific file version.
701
     */
702
    public BoxFileVersion getVersionByID(String fileVersionID, String... fields) {
703
        URL url = BoxFileVersion.VERSION_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), fileVersionID);
1✔
704
        if (fields.length > 0) {
1✔
705
            String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
×
706
            url = BoxFileVersion.VERSION_URL_TEMPLATE.buildWithQuery(
×
707
                this.getAPI().getBaseURL(),
×
708
                queryString,
709
                this.getID(),
×
710
                fileVersionID
711
            );
712
        }
713

714
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
715
        try (BoxJSONResponse response = request.send()) {
1✔
716
            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
1✔
717
            return new BoxFileVersion(this.getAPI(), jsonObject, this.getID());
1✔
718
        }
719
    }
720

721
    /**
722
     * Gets up to 1000 versions of this file. Note that only users with premium accounts will be able to retrieve
723
     * previous versions of their files. `fields` parameter is optional, if specified only requested fields will
724
     * be returned:
725
     * <pre>
726
     * {@code
727
     * new BoxFile(api, file_id).getVersions()       // will return all default fields
728
     * new BoxFile(api, file_id).getVersions("name") // will return only specified fields
729
     * }
730
     * </pre>
731
     *
732
     * @param fields the fields to retrieve. If nothing provided default fields will be returned.
733
     *               You can find list of available fields at {@link BoxFile#ALL_VERSION_FIELDS}
734
     * @return a list of previous file versions.
735
     */
736
    public Collection<BoxFileVersion> getVersions(String... fields) {
737
        return getVersionsRange(0, BoxFileVersion.DEFAULT_LIMIT, fields);
1✔
738
    }
739

740

741
    /**
742
     * Retrieves a specific range of versions of this file.
743
     *
744
     * @param offset the index of the first version of this file to retrieve.
745
     * @param limit  the maximum number of versions to retrieve after the offset.
746
     * @param fields the fields to retrieve.
747
     * @return a partial collection containing the specified range of versions of this file.
748
     */
749
    public PartialCollection<BoxFileVersion> getVersionsRange(long offset, long limit, String... fields) {
750
        QueryStringBuilder builder = new QueryStringBuilder()
1✔
751
            .appendParam("limit", limit)
1✔
752
            .appendParam("offset", offset);
1✔
753

754
        if (fields.length > 0) {
1✔
755
            builder.appendParam("fields", fields);
1✔
756
        }
757

758
        URL url = VERSIONS_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID());
1✔
759
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
760
        try (BoxJSONResponse response = request.send()) {
1✔
761

762
            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
1✔
763
            String totalCountString = jsonObject.get("total_count").toString();
1✔
764
            long fullSize = Double.valueOf(totalCountString).longValue();
1✔
765
            PartialCollection<BoxFileVersion> versions = new PartialCollection<>(offset, limit, fullSize);
1✔
766
            JsonArray entries = jsonObject.get("entries").asArray();
1✔
767
            for (JsonValue entry : entries) {
1✔
768
                versions.add(new BoxFileVersion(this.getAPI(), entry.asObject(), this.getID()));
1✔
769
            }
1✔
770

771
            return versions;
1✔
772
        }
773
    }
774

775
    /**
776
     * Checks if a new version of the file can be uploaded with the specified name.
777
     *
778
     * @param name the new name for the file.
779
     * @return whether or not the file version can be uploaded.
780
     */
781
    public boolean canUploadVersion(String name) {
782
        return this.canUploadVersion(name, 0);
×
783
    }
784

785
    /**
786
     * Checks if a new version of the file can be uploaded with the specified name and size.
787
     *
788
     * @param name     the new name for the file.
789
     * @param fileSize the size of the new version content in bytes.
790
     * @return whether the file version can be uploaded.
791
     */
792
    public boolean canUploadVersion(String name, long fileSize) {
793

794
        URL url = getDownloadUrl();
×
795
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
×
796

797
        JsonObject preflightInfo = new JsonObject();
×
798
        if (name != null) {
×
799
            preflightInfo.add("name", name);
×
800
        }
801

802
        preflightInfo.add("size", fileSize);
×
803

804
        request.setBody(preflightInfo.toString());
×
805
        try (BoxAPIResponse response = request.send()) {
×
806
            return response.getResponseCode() == 200;
×
807
        } catch (BoxAPIException ex) {
×
808
            if (ex.getResponseCode() >= 400 && ex.getResponseCode() < 500) {
×
809
                // This looks like an error response, meaning the upload would fail
810
                return false;
×
811
            } else {
812
                // This looks like a network error or server error, rethrow exception
813
                throw ex;
×
814
            }
815
        }
816
    }
817

818
    /**
819
     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
820
     * will be able to view and recover previous versions of the file.
821
     *
822
     * @param fileContent a stream containing the new file contents.
823
     * @return the uploaded file version.
824
     */
825
    public BoxFile.Info uploadNewVersion(InputStream fileContent) {
826
        return this.uploadNewVersion(fileContent, null);
1✔
827
    }
828

829
    /**
830
     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
831
     * will be able to view and recover previous versions of the file.
832
     *
833
     * @param fileContent     a stream containing the new file contents.
834
     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
835
     * @return the uploaded file version.
836
     */
837
    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1) {
838
        return this.uploadNewVersion(fileContent, fileContentSHA1, null);
1✔
839
    }
840

841
    /**
842
     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
843
     * will be able to view and recover previous versions of the file.
844
     *
845
     * @param fileContent     a stream containing the new file contents.
846
     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
847
     * @param modified        the date that the new version was modified.
848
     * @return the uploaded file version.
849
     */
850
    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified) {
851
        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, 0, null);
1✔
852
    }
853

854
    /**
855
     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
856
     * will be able to view and recover previous versions of the file.
857
     *
858
     * @param fileContent     a stream containing the new file contents.
859
     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
860
     * @param modified        the date that the new version was modified.
861
     * @param name            the new name for the file
862
     * @return the uploaded file version.
863
     */
864
    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, String name) {
865
        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, name, 0, null);
×
866
    }
867

868
    /**
869
     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
870
     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
871
     * of the file.
872
     *
873
     * @param fileContent a stream containing the new file contents.
874
     * @param modified    the date that the new version was modified.
875
     * @param fileSize    the size of the file used for determining the progress of the upload.
876
     * @param listener    a listener for monitoring the upload's progress.
877
     * @return the uploaded file version.
878
     */
879
    public BoxFile.Info uploadNewVersion(InputStream fileContent, Date modified, long fileSize,
880
                                         ProgressListener listener) {
881
        return this.uploadNewVersion(fileContent, null, modified, fileSize, listener);
×
882
    }
883

884
    /**
885
     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
886
     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
887
     * of the file.
888
     *
889
     * @param fileContent     a stream containing the new file contents.
890
     * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header
891
     * @param modified        the date that the new version was modified.
892
     * @param fileSize        the size of the file used for determining the progress of the upload.
893
     * @param listener        a listener for monitoring the upload's progress.
894
     * @return the uploaded file version.
895
     */
896
    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, long fileSize,
897
                                         ProgressListener listener) {
898
        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, null, fileSize, listener);
1✔
899
    }
900

901
    /**
902
     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
903
     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
904
     * of the file.
905
     *
906
     * @param fileContent     a stream containing the new file contents.
907
     * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header
908
     * @param modified        the date that the new version was modified.
909
     * @param name            the new name for the file
910
     * @param fileSize        the size of the file used for determining the progress of the upload.
911
     * @param listener        a listener for monitoring the upload's progress.
912
     * @return the uploaded file version.
913
     */
914
    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, String name,
915
                                         long fileSize, ProgressListener listener) {
916
        URL uploadURL = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1✔
917
        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
1✔
918

919
        if (fileSize > 0) {
1✔
920
            request.setFile(fileContent, "", fileSize);
×
921
        } else {
922
            request.setFile(fileContent, "");
1✔
923
        }
924

925
        if (fileContentSHA1 != null) {
1✔
926
            request.setContentSHA1(fileContentSHA1);
×
927
        }
928

929
        JsonObject attributesJSON = new JsonObject();
1✔
930
        if (modified != null) {
1✔
931
            attributesJSON.add("content_modified_at", BoxDateFormat.format(modified));
×
932
        }
933

934
        if (name != null) {
1✔
935
            attributesJSON.add("name", name);
×
936
        }
937

938
        request.putField("attributes", attributesJSON.toString());
1✔
939

940
        BoxJSONResponse response = null;
1✔
941
        try {
942
            if (listener == null) {
1✔
943
                // upload is multipart request but response is JSON
944
                response = (BoxJSONResponse) request.send();
1✔
945
            } else {
946
                // upload is multipart request but response is JSON
947
                response = (BoxJSONResponse) request.send(listener);
×
948
            }
949

950
            String fileJSON = response.getJsonObject().get("entries").asArray().get(0).toString();
1✔
951

952
            return new BoxFile.Info(fileJSON);
1✔
953
        } finally {
954
            Optional.ofNullable(response).ifPresent(BoxAPIResponse::close);
1✔
955
        }
956
    }
957

958
    /**
959
     * Gets an expiring URL for creating an embedded preview session. The URL will expire after 60 seconds and the
960
     * preview session will expire after 60 minutes.
961
     *
962
     * @return the expiring preview link
963
     */
964
    public URL getPreviewLink() {
965
        BoxFile.Info info = this.getInfo("expiring_embed_link");
×
966

967
        return info.getPreviewLink();
×
968
    }
969

970
    /**
971
     * Gets a list of any comments on this file.
972
     *
973
     * @return a list of comments on this file.
974
     */
975
    public List<BoxComment.Info> getComments() {
976
        URL url = GET_COMMENTS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
977
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
978
        try (BoxJSONResponse response = request.send()) {
1✔
979
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1✔
980

981
            int totalCount = responseJSON.get("total_count").asInt();
1✔
982
            List<BoxComment.Info> comments = new ArrayList<>(totalCount);
1✔
983
            JsonArray entries = responseJSON.get("entries").asArray();
1✔
984
            for (JsonValue value : entries) {
1✔
985
                JsonObject commentJSON = value.asObject();
1✔
986
                BoxComment comment = new BoxComment(this.getAPI(), commentJSON.get("id").asString());
1✔
987
                BoxComment.Info info = comment.new Info(commentJSON);
1✔
988
                comments.add(info);
1✔
989
            }
1✔
990

991
            return comments;
1✔
992
        }
993
    }
994

995
    /**
996
     * Gets a list of any tasks on this file with requested fields.
997
     *
998
     * @param fields optional fields to retrieve for this task.
999
     * @return a list of tasks on this file.
1000
     */
1001
    public List<BoxTask.Info> getTasks(String... fields) {
1002
        QueryStringBuilder builder = new QueryStringBuilder();
1✔
1003
        if (fields.length > 0) {
1✔
1004
            builder.appendParam("fields", fields);
1✔
1005
        }
1006
        URL url = GET_TASKS_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID());
1✔
1007
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
1008
        try (BoxJSONResponse response = request.send()) {
1✔
1009
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1✔
1010

1011
            int totalCount = responseJSON.get("total_count").asInt();
1✔
1012
            List<BoxTask.Info> tasks = new ArrayList<>(totalCount);
1✔
1013
            JsonArray entries = responseJSON.get("entries").asArray();
1✔
1014
            for (JsonValue value : entries) {
1✔
1015
                JsonObject taskJSON = value.asObject();
1✔
1016
                BoxTask task = new BoxTask(this.getAPI(), taskJSON.get("id").asString());
1✔
1017
                BoxTask.Info info = task.new Info(taskJSON);
1✔
1018
                tasks.add(info);
1✔
1019
            }
1✔
1020

1021
            return tasks;
1✔
1022
        }
1023
    }
1024

1025
    /**
1026
     * Creates metadata on this file in the global properties template.
1027
     *
1028
     * @param metadata The new metadata values.
1029
     * @return the metadata returned from the server.
1030
     */
1031
    public Metadata createMetadata(Metadata metadata) {
1032
        return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata);
1✔
1033
    }
1034

1035
    /**
1036
     * Creates metadata on this file in the specified template type.
1037
     *
1038
     * @param typeName the metadata template type name.
1039
     * @param metadata the new metadata values.
1040
     * @return the metadata returned from the server.
1041
     */
1042
    public Metadata createMetadata(String typeName, Metadata metadata) {
1043
        String scope = Metadata.scopeBasedOnType(typeName);
1✔
1044
        return this.createMetadata(typeName, scope, metadata);
1✔
1045
    }
1046

1047
    /**
1048
     * Creates metadata on this file in the specified template type.
1049
     *
1050
     * @param typeName the metadata template type name.
1051
     * @param scope    the metadata scope (global or enterprise).
1052
     * @param metadata the new metadata values.
1053
     * @return the metadata returned from the server.
1054
     */
1055
    public Metadata createMetadata(String typeName, String scope, Metadata metadata) {
1056
        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1✔
1057
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1✔
1058
        request.setBody(metadata.toString());
1✔
1059
        try (BoxJSONResponse response = request.send()) {
1✔
1060
            return new Metadata(Json.parse(response.getJSON()).asObject());
1✔
1061
        }
1062
    }
1063

1064
    /**
1065
     * Sets the provided metadata on the file. If metadata has already been created on this file,
1066
     * it overwrites metadata keys specified in the `metadata` param.
1067
     *
1068
     * @param templateName the name of the metadata template.
1069
     * @param scope        the scope of the template (usually "global" or "enterprise").
1070
     * @param metadata     the new metadata values.
1071
     * @return the metadata returned from the server.
1072
     */
1073
    public Metadata setMetadata(String templateName, String scope, Metadata metadata) {
1074
        try {
1075
            return this.createMetadata(templateName, scope, metadata);
×
1076
        } catch (BoxAPIException e) {
1✔
1077
            if (e.getResponseCode() == 409) {
1✔
1078
                if (metadata.getOperations().isEmpty()) {
1✔
1079
                    return getMetadata();
1✔
1080
                } else {
1081
                    return updateExistingTemplate(templateName, scope, metadata);
1✔
1082
                }
1083
            } else {
1084
                throw e;
×
1085
            }
1086
        }
1087
    }
1088

1089
    private Metadata updateExistingTemplate(String templateName, String scope, Metadata metadata) {
1090
        Metadata metadataToUpdate = new Metadata(scope, templateName);
1✔
1091
        for (JsonValue value : metadata.getOperations()) {
1✔
1092
            if (value.asObject().get("value").isNumber()) {
1✔
1093
                metadataToUpdate.add(value.asObject().get("path").asString(),
1✔
1094
                    value.asObject().get("value").asDouble());
1✔
1095
            } else if (value.asObject().get("value").isString()) {
1✔
1096
                metadataToUpdate.add(value.asObject().get("path").asString(),
1✔
1097
                    value.asObject().get("value").asString());
1✔
1098
            } else if (value.asObject().get("value").isArray()) {
1✔
1099
                ArrayList<String> list = new ArrayList<>();
1✔
1100
                for (JsonValue jsonValue : value.asObject().get("value").asArray()) {
1✔
1101
                    list.add(jsonValue.asString());
1✔
1102
                }
1✔
1103
                metadataToUpdate.add(value.asObject().get("path").asString(), list);
1✔
1104
            }
1105
        }
1✔
1106
        return this.updateMetadata(metadataToUpdate);
1✔
1107
    }
1108

1109
    /**
1110
     * Adds a metadata classification to the specified file.
1111
     *
1112
     * @param classificationType the metadata classification type.
1113
     * @return the metadata classification type added to the file.
1114
     */
1115
    public String addClassification(String classificationType) {
1116
        Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType);
1✔
1117
        Metadata classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY,
1✔
1118
            "enterprise", metadata);
1119

1120
        return classification.getString(Metadata.CLASSIFICATION_KEY);
1✔
1121
    }
1122

1123
    /**
1124
     * Updates a metadata classification on the specified file.
1125
     *
1126
     * @param classificationType the metadata classification type.
1127
     * @return the new metadata classification type updated on the file.
1128
     */
1129
    public String updateClassification(String classificationType) {
1130
        Metadata metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY);
1✔
1131
        metadata.add("/Box__Security__Classification__Key", classificationType);
1✔
1132
        Metadata classification = this.updateMetadata(metadata);
1✔
1133

1134
        return classification.getString(Metadata.CLASSIFICATION_KEY);
1✔
1135
    }
1136

1137
    /**
1138
     * Attempts to add classification to a file. If classification already exists then do update.
1139
     *
1140
     * @param classificationType the metadata classification type.
1141
     * @return the metadata classification type on the file.
1142
     */
1143
    public String setClassification(String classificationType) {
1144
        Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType);
1✔
1145
        Metadata classification;
1146

1147
        try {
1148
            classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise", metadata);
×
1149
        } catch (BoxAPIException e) {
1✔
1150
            if (e.getResponseCode() == 409) {
1✔
1151
                metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY);
1✔
1152
                metadata.replace(Metadata.CLASSIFICATION_KEY, classificationType);
1✔
1153
                classification = this.updateMetadata(metadata);
1✔
1154
            } else {
1155
                throw e;
1✔
1156
            }
1157
        }
×
1158

1159
        return classification.getString(Metadata.CLASSIFICATION_KEY);
1✔
1160
    }
1161

1162
    /**
1163
     * Gets the classification type for the specified file.
1164
     *
1165
     * @return the metadata classification type on the file.
1166
     */
1167
    public String getClassification() {
1168
        Metadata metadata;
1169
        try {
1170
            metadata = this.getMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY);
1✔
1171

1172
        } catch (BoxAPIException e) {
1✔
1173
            JsonObject responseObject = Json.parse(e.getResponse()).asObject();
1✔
1174
            String code = responseObject.get("code").asString();
1✔
1175

1176
            if (e.getResponseCode() == 404 && code.equals("instance_not_found")) {
1✔
1177
                return null;
1✔
1178
            } else {
1179
                throw e;
1✔
1180
            }
1181
        }
1✔
1182

1183
        return metadata.getString(Metadata.CLASSIFICATION_KEY);
1✔
1184
    }
1185

1186
    /**
1187
     * Deletes the classification on the file.
1188
     */
1189
    public void deleteClassification() {
1190
        this.deleteMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise");
1✔
1191
    }
1✔
1192

1193
    /**
1194
     * Locks a file.
1195
     *
1196
     * @return the lock returned from the server.
1197
     */
1198
    public BoxLock lock() {
1199
        return this.lock(null, false);
×
1200
    }
1201

1202
    /**
1203
     * Locks a file.
1204
     *
1205
     * @param isDownloadPrevented is downloading of file prevented when locked.
1206
     * @return the lock returned from the server.
1207
     */
1208
    public BoxLock lock(boolean isDownloadPrevented) {
1209
        return this.lock(null, isDownloadPrevented);
1✔
1210
    }
1211

1212
    /**
1213
     * Locks a file.
1214
     *
1215
     * @param expiresAt expiration date of the lock.
1216
     * @return the lock returned from the server.
1217
     */
1218
    public BoxLock lock(Date expiresAt) {
1219
        return this.lock(expiresAt, false);
×
1220
    }
1221

1222
    /**
1223
     * Locks a file.
1224
     *
1225
     * @param expiresAt           expiration date of the lock.
1226
     * @param isDownloadPrevented is downloading of file prevented when locked.
1227
     * @return the lock returned from the server.
1228
     */
1229
    public BoxLock lock(Date expiresAt, boolean isDownloadPrevented) {
1230
        String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString();
1✔
1231
        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1✔
1232
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
1✔
1233

1234
        JsonObject lockConfig = new JsonObject();
1✔
1235
        lockConfig.add("type", "lock");
1✔
1236
        if (expiresAt != null) {
1✔
1237
            lockConfig.add("expires_at", BoxDateFormat.format(expiresAt));
×
1238
        }
1239
        lockConfig.add("is_download_prevented", isDownloadPrevented);
1✔
1240

1241
        JsonObject requestJSON = new JsonObject();
1✔
1242
        requestJSON.add("lock", lockConfig);
1✔
1243
        request.setBody(requestJSON.toString());
1✔
1244

1245
        try (BoxJSONResponse response = request.send()) {
1✔
1246

1247
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1✔
1248
            JsonValue lockValue = responseJSON.get("lock");
1✔
1249
            JsonObject lockJSON = Json.parse(lockValue.toString()).asObject();
1✔
1250

1251
            return new BoxLock(lockJSON, this.getAPI());
1✔
1252
        }
1253
    }
1254

1255
    /**
1256
     * Unlocks a file.
1257
     */
1258
    public void unlock() {
1259
        String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString();
1✔
1260
        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1✔
1261
        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
1✔
1262

1263
        JsonObject lockObject = new JsonObject();
1✔
1264
        lockObject.add("lock", NULL);
1✔
1265

1266
        request.setBody(lockObject.toString());
1✔
1267
        request.send().close();
1✔
1268
    }
1✔
1269

1270
    /**
1271
     * Used to retrieve all metadata associated with the file.
1272
     *
1273
     * @param fields the optional fields to retrieve.
1274
     * @return An iterable of metadata instances associated with the file.
1275
     */
1276
    public Iterable<Metadata> getAllMetadata(String... fields) {
1277
        return Metadata.getAllMetadata(this, fields);
×
1278
    }
1279

1280
    /**
1281
     * Gets the file properties metadata.
1282
     *
1283
     * @return the metadata returned from the server.
1284
     */
1285
    public Metadata getMetadata() {
1286
        return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE);
1✔
1287
    }
1288

1289
    /**
1290
     * Gets the file metadata of specified template type.
1291
     *
1292
     * @param typeName the metadata template type name.
1293
     * @return the metadata returned from the server.
1294
     */
1295
    public Metadata getMetadata(String typeName) {
1296
        String scope = Metadata.scopeBasedOnType(typeName);
1✔
1297
        return this.getMetadata(typeName, scope);
1✔
1298
    }
1299

1300
    /**
1301
     * Gets the file metadata of specified template type.
1302
     *
1303
     * @param typeName the metadata template type name.
1304
     * @param scope    the metadata scope (global or enterprise).
1305
     * @return the metadata returned from the server.
1306
     */
1307
    public Metadata getMetadata(String typeName, String scope) {
1308
        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1✔
1309
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
1310
        try (BoxJSONResponse response = request.send()) {
1✔
1311
            return new Metadata(Json.parse(response.getJSON()).asObject());
1✔
1312
        }
1313
    }
1314

1315
    /**
1316
     * Updates the file metadata.
1317
     *
1318
     * @param metadata the new metadata values.
1319
     * @return the metadata returned from the server.
1320
     */
1321
    public Metadata updateMetadata(Metadata metadata) {
1322
        String scope;
1323
        if (metadata.getScope().equals(Metadata.GLOBAL_METADATA_SCOPE)) {
1✔
1324
            scope = Metadata.GLOBAL_METADATA_SCOPE;
×
1325
        } else if (metadata.getScope().startsWith(Metadata.ENTERPRISE_METADATA_SCOPE)) {
1✔
1326
            scope = metadata.getScope();
1✔
1327
        } else {
1328
            scope = Metadata.ENTERPRISE_METADATA_SCOPE;
×
1329
        }
1330

1331
        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(),
1✔
1332
            scope, metadata.getTemplateName());
1✔
1333
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT", APPLICATION_JSON_PATCH);
1✔
1334
        request.setBody(metadata.getPatch());
1✔
1335
        try (BoxJSONResponse response = request.send()) {
1✔
1336
            return new Metadata(Json.parse(response.getJSON()).asObject());
1✔
1337
        }
1338
    }
1339

1340
    /**
1341
     * Deletes the file properties metadata.
1342
     */
1343
    public void deleteMetadata() {
1344
        this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE);
1✔
1345
    }
1✔
1346

1347
    /**
1348
     * Deletes the file metadata of specified template type.
1349
     *
1350
     * @param typeName the metadata template type name.
1351
     */
1352
    public void deleteMetadata(String typeName) {
1353
        String scope = Metadata.scopeBasedOnType(typeName);
1✔
1354
        this.deleteMetadata(typeName, scope);
1✔
1355
    }
1✔
1356

1357
    /**
1358
     * Deletes the file metadata of specified template type.
1359
     *
1360
     * @param typeName the metadata template type name.
1361
     * @param scope    the metadata scope (global or enterprise).
1362
     */
1363
    public void deleteMetadata(String typeName, String scope) {
1364
        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1✔
1365
        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
1✔
1366
        request.send().close();
1✔
1367
    }
1✔
1368

1369
    /**
1370
     * Used to retrieve the watermark for the file.
1371
     * If the file does not have a watermark applied to it, a 404 Not Found will be returned by API.
1372
     *
1373
     * @param fields the fields to retrieve.
1374
     * @return the watermark associated with the file.
1375
     */
1376
    public BoxWatermark getWatermark(String... fields) {
1377
        return this.getWatermark(FILE_URL_TEMPLATE, fields);
1✔
1378
    }
1379

1380
    /**
1381
     * Used to apply or update the watermark for the file.
1382
     *
1383
     * @return the watermark associated with the file.
1384
     */
1385
    public BoxWatermark applyWatermark() {
1386
        return this.applyWatermark(FILE_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT);
1✔
1387
    }
1388

1389
    /**
1390
     * Removes a watermark from the file.
1391
     * If the file did not have a watermark applied to it, a 404 Not Found will be returned by API.
1392
     */
1393
    public void removeWatermark() {
1394
        this.removeWatermark(FILE_URL_TEMPLATE);
1✔
1395
    }
1✔
1396

1397
    /**
1398
     * {@inheritDoc}
1399
     */
1400
    @Override
1401
    public BoxFile.Info setCollections(BoxCollection... collections) {
1402
        JsonArray jsonArray = new JsonArray();
×
1403
        for (BoxCollection collection : collections) {
×
1404
            JsonObject collectionJSON = new JsonObject();
×
1405
            collectionJSON.add("id", collection.getID());
×
1406
            jsonArray.add(collectionJSON);
×
1407
        }
1408
        JsonObject infoJSON = new JsonObject();
×
1409
        infoJSON.add("collections", jsonArray);
×
1410

1411
        String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString();
×
1412
        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
×
1413
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
×
1414
        request.setBody(infoJSON.toString());
×
1415
        try (BoxJSONResponse response = request.send()) {
×
1416
            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
×
1417
            return new Info(jsonObject);
×
1418
        }
1419
    }
1420

1421
    /**
1422
     * Creates an upload session to create a new version of a file in chunks.
1423
     * This will first verify that the version can be created and then open a session for uploading pieces of the file.
1424
     *
1425
     * @param fileSize the size of the file that will be uploaded.
1426
     * @return the created upload session instance.
1427
     */
1428
    public BoxFileUploadSession.Info createUploadSession(long fileSize) {
1429
        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
×
1430

1431
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
×
1432
        request.addHeader("Content-Type", APPLICATION_JSON);
×
1433

1434
        JsonObject body = new JsonObject();
×
1435
        body.add("file_size", fileSize);
×
1436
        request.setBody(body.toString());
×
1437

1438
        try (BoxJSONResponse response = request.send()) {
×
1439
            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
×
1440

1441
            String sessionId = jsonObject.get("id").asString();
×
1442
            BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId);
×
1443
            return session.new Info(jsonObject);
×
1444
        }
1445
    }
1446

1447
    /**
1448
     * Creates a new version of a file.
1449
     *
1450
     * @param inputStream the stream instance that contains the data.
1451
     * @param fileSize    the size of the file that will be uploaded.
1452
     * @return the created file instance.
1453
     * @throws InterruptedException when a thread execution is interrupted.
1454
     * @throws IOException          when reading a stream throws exception.
1455
     */
1456
    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize)
1457
        throws InterruptedException, IOException {
1458
        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
×
1459
        return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize);
×
1460
    }
1461

1462
    /**
1463
     * Creates a new version of a file.  Also sets file attributes.
1464
     *
1465
     * @param inputStream    the stream instance that contains the data.
1466
     * @param fileSize       the size of the file that will be uploaded.
1467
     * @param fileAttributes file attributes to set
1468
     * @return the created file instance.
1469
     * @throws InterruptedException when a thread execution is interrupted.
1470
     * @throws IOException          when reading a stream throws exception.
1471
     */
1472
    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize, Map<String, String> fileAttributes)
1473
        throws InterruptedException, IOException {
1474
        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1✔
1475
        return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize, fileAttributes);
1✔
1476
    }
1477

1478
    /**
1479
     * Creates a new version of a file using specified number of parallel http connections.
1480
     *
1481
     * @param inputStream          the stream instance that contains the data.
1482
     * @param fileSize             the size of the file that will be uploaded.
1483
     * @param nParallelConnections number of parallel http connections to use
1484
     * @param timeOut              time to wait before killing the job
1485
     * @param unit                 time unit for the time wait value
1486
     * @return the created file instance.
1487
     * @throws InterruptedException when a thread execution is interrupted.
1488
     * @throws IOException          when reading a stream throws exception.
1489
     */
1490
    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize,
1491
                                        int nParallelConnections, long timeOut, TimeUnit unit)
1492
        throws InterruptedException, IOException {
1493
        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
×
1494
        return new LargeFileUpload(nParallelConnections, timeOut, unit)
×
1495
            .upload(this.getAPI(), inputStream, url, fileSize);
×
1496
    }
1497

1498
    /**
1499
     * Creates a new version of a file using specified number of parallel http connections.  Also sets file attributes.
1500
     *
1501
     * @param inputStream          the stream instance that contains the data.
1502
     * @param fileSize             the size of the file that will be uploaded.
1503
     * @param nParallelConnections number of parallel http connections to use
1504
     * @param timeOut              time to wait before killing the job
1505
     * @param unit                 time unit for the time wait value
1506
     * @param fileAttributes       file attributes to set
1507
     * @return the created file instance.
1508
     * @throws InterruptedException when a thread execution is interrupted.
1509
     * @throws IOException          when reading a stream throws exception.
1510
     */
1511
    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize,
1512
                                        int nParallelConnections, long timeOut, TimeUnit unit,
1513
                                        Map<String, String> fileAttributes)
1514
        throws InterruptedException, IOException {
1515
        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
×
1516
        return new LargeFileUpload(nParallelConnections, timeOut, unit)
×
1517
            .upload(this.getAPI(), inputStream, url, fileSize, fileAttributes);
×
1518
    }
1519

1520
    private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role,
1521
                                              Boolean notify, Boolean canViewPath, Date expiresAt,
1522
                                              Boolean isAccessOnly) {
1523

1524
        JsonObject itemField = new JsonObject();
1✔
1525
        itemField.add("id", this.getID());
1✔
1526
        itemField.add("type", "file");
1✔
1527

1528
        return BoxCollaboration.create(this.getAPI(), accessibleByField, itemField, role, notify, canViewPath,
1✔
1529
            expiresAt, isAccessOnly);
1530
    }
1531

1532
    /**
1533
     * Adds a collaborator to this file.
1534
     *
1535
     * @param collaborator the collaborator to add.
1536
     * @param role         the role of the collaborator.
1537
     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1538
     * @param canViewPath  whether view path collaboration feature is enabled or not.
1539
     * @param expiresAt    when the collaboration should expire.
1540
     * @param isAccessOnly whether the collaboration is access only or not.
1541
     * @return info about the new collaboration.
1542
     */
1543
    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role,
1544
                                             Boolean notify, Boolean canViewPath,
1545
                                             Date expiresAt, Boolean isAccessOnly) {
1546
        JsonObject accessibleByField = new JsonObject();
1✔
1547
        accessibleByField.add("id", collaborator.getID());
1✔
1548

1549
        if (collaborator instanceof BoxUser) {
1✔
1550
            accessibleByField.add("type", "user");
1✔
1551
        } else if (collaborator instanceof BoxGroup) {
×
1552
            accessibleByField.add("type", "group");
×
1553
        } else {
1554
            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
×
1555
        }
1556
        return this.collaborate(accessibleByField, role, notify, canViewPath, expiresAt, isAccessOnly);
1✔
1557
    }
1558

1559
    /**
1560
     * Adds a collaborator to this file.
1561
     *
1562
     * @param collaborator the collaborator to add.
1563
     * @param role         the role of the collaborator.
1564
     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1565
     * @param canViewPath  whether view path collaboration feature is enabled or not.
1566
     * @return info about the new collaboration.
1567
     */
1568
    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role,
1569
                                             Boolean notify, Boolean canViewPath) {
1570
        return this.collaborate(collaborator, role, notify, canViewPath, null, null);
×
1571
    }
1572

1573
    /**
1574
     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
1575
     * account.
1576
     *
1577
     * @param email        the email address of the collaborator to add.
1578
     * @param role         the role of the collaborator.
1579
     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1580
     * @param canViewPath  whether view path collaboration feature is enabled or not.
1581
     * @param expiresAt    when the collaboration should expire.
1582
     * @param isAccessOnly whether the collaboration is access only or not.
1583
     * @return info about the new collaboration.
1584
     */
1585
    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role,
1586
                                             Boolean notify, Boolean canViewPath,
1587
                                             Date expiresAt, Boolean isAccessOnly) {
1588
        JsonObject accessibleByField = new JsonObject();
1✔
1589
        accessibleByField.add("login", email);
1✔
1590
        accessibleByField.add("type", "user");
1✔
1591

1592
        return this.collaborate(accessibleByField, role, notify, canViewPath, expiresAt, isAccessOnly);
1✔
1593
    }
1594

1595
    /**
1596
     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
1597
     * account.
1598
     *
1599
     * @param email       the email address of the collaborator to add.
1600
     * @param role        the role of the collaborator.
1601
     * @param notify      determines if the user (or all the users in the group) will receive email notifications.
1602
     * @param canViewPath whether view path collaboration feature is enabled or not.
1603
     * @return info about the new collaboration.
1604
     */
1605
    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role,
1606
                                             Boolean notify, Boolean canViewPath) {
1607
        return this.collaborate(email, role, notify, canViewPath, null, null);
×
1608
    }
1609

1610
    /**
1611
     * Used to retrieve all collaborations associated with the item.
1612
     *
1613
     * @param fields the optional fields to retrieve.
1614
     * @return An iterable of metadata instances associated with the item.
1615
     */
1616
    public BoxResourceIterable<BoxCollaboration.Info> getAllFileCollaborations(String... fields) {
1617
        return BoxCollaboration.getAllFileCollaborations(this.getAPI(), this.getID(),
×
1618
            GET_COLLABORATORS_PAGE_SIZE, fields);
1619

1620
    }
1621

1622
    /**
1623
     * Used to specify what filetype to request for a file thumbnail.
1624
     */
1625
    public enum ThumbnailFileType {
×
1626
        /**
1627
         * PNG image format.
1628
         */
1629
        PNG,
×
1630

1631
        /**
1632
         * JPG image format.
1633
         */
1634
        JPG
×
1635
    }
1636

1637
    /**
1638
     * Enumerates the possible permissions that a user can have on a file.
1639
     */
1640
    public enum Permission {
1✔
1641
        /**
1642
         * The user can download the file.
1643
         */
1644
        CAN_DOWNLOAD("can_download"),
1✔
1645

1646
        /**
1647
         * The user can upload new versions of the file.
1648
         */
1649
        CAN_UPLOAD("can_upload"),
1✔
1650

1651
        /**
1652
         * The user can rename the file.
1653
         */
1654
        CAN_RENAME("can_rename"),
1✔
1655

1656
        /**
1657
         * The user can delete the file.
1658
         */
1659
        CAN_DELETE("can_delete"),
1✔
1660

1661
        /**
1662
         * The user can share the file.
1663
         */
1664
        CAN_SHARE("can_share"),
1✔
1665

1666
        /**
1667
         * The user can set the access level for shared links to the file.
1668
         */
1669
        CAN_SET_SHARE_ACCESS("can_set_share_access"),
1✔
1670

1671
        /**
1672
         * The user can preview the file.
1673
         */
1674
        CAN_PREVIEW("can_preview"),
1✔
1675

1676
        /**
1677
         * The user can comment on the file.
1678
         */
1679
        CAN_COMMENT("can_comment"),
1✔
1680

1681
        /**
1682
         * The user can place annotations on this file.
1683
         */
1684
        CAN_ANNOTATE("can_annotate"),
1✔
1685

1686
        /**
1687
         * The current user can invite new users to collaborate on this item, and the user can update the role of a
1688
         * user already collaborated on this item.
1689
         */
1690
        CAN_INVITE_COLLABORATOR("can_invite_collaborator"),
1✔
1691

1692
        /**
1693
         * The user can view all annotations placed on this file.
1694
         */
1695
        CAN_VIEW_ANNOTATIONS_ALL("can_view_annotations_all"),
1✔
1696

1697
        /**
1698
         * The user can view annotations placed by themselves on this file.
1699
         */
1700
        CAN_VIEW_ANNOTATIONS_SELF("can_view_annotations_self");
1✔
1701

1702
        private final String jsonValue;
1703

1704
        Permission(String jsonValue) {
1✔
1705
            this.jsonValue = jsonValue;
1✔
1706
        }
1✔
1707

1708
        static Permission fromJSONValue(String jsonValue) {
1709
            return Permission.valueOf(jsonValue.toUpperCase());
1✔
1710
        }
1711

1712
        String toJSONValue() {
1713
            return this.jsonValue;
×
1714
        }
1715
    }
1716

1717
    /**
1718
     * Contains information about a BoxFile.
1719
     */
1720
    public class Info extends BoxItem.Info {
1721
        private String sha1;
1722
        private String versionNumber;
1723
        private long commentCount;
1724
        private EnumSet<Permission> permissions;
1725
        private String extension;
1726
        private boolean isPackage;
1727
        private BoxFileVersion version;
1728
        private URL previewLink;
1729
        private BoxLock lock;
1730
        private boolean isWatermarked;
1731
        private boolean isExternallyOwned;
1732
        private Map<String, Map<String, Metadata>> metadataMap;
1733
        private List<Representation> representations;
1734
        private List<String> allowedInviteeRoles;
1735
        private Boolean hasCollaborations;
1736
        private String uploaderDisplayName;
1737
        private BoxClassification classification;
1738
        private Date dispositionAt;
1739
        private boolean isAccessibleViaSharedLink;
1740

1741
        /**
1742
         * Constructs an empty Info object.
1743
         */
1744
        public Info() {
1✔
1745
            super();
1✔
1746
        }
1✔
1747

1748
        /**
1749
         * Constructs an Info object by parsing information from a JSON string.
1750
         *
1751
         * @param json the JSON string to parse.
1752
         */
1753
        public Info(String json) {
1✔
1754
            super(json);
1✔
1755
        }
1✔
1756

1757
        /**
1758
         * Constructs an Info object using an already parsed JSON object.
1759
         *
1760
         * @param jsonObject the parsed JSON object.
1761
         */
1762
        public Info(JsonObject jsonObject) {
1✔
1763
            super(jsonObject);
1✔
1764
        }
1✔
1765

1766
        @Override
1767
        public BoxFile getResource() {
1768
            return BoxFile.this;
1✔
1769
        }
1770

1771
        /**
1772
         * Gets the SHA1 hash of the file.
1773
         *
1774
         * @return the SHA1 hash of the file.
1775
         */
1776
        public String getSha1() {
1777
            return this.sha1;
×
1778
        }
1779

1780
        /**
1781
         * Gets the lock of the file.
1782
         *
1783
         * @return the lock of the file.
1784
         */
1785
        public BoxLock getLock() {
1786
            return this.lock;
1✔
1787
        }
1788

1789
        /**
1790
         * Gets the current version number of the file.
1791
         *
1792
         * @return the current version number of the file.
1793
         */
1794
        public String getVersionNumber() {
1795
            return this.versionNumber;
×
1796
        }
1797

1798
        /**
1799
         * Gets the number of comments on the file.
1800
         *
1801
         * @return the number of comments on the file.
1802
         */
1803
        public long getCommentCount() {
1804
            return this.commentCount;
×
1805
        }
1806

1807
        /**
1808
         * Gets the permissions that the current user has on the file.
1809
         *
1810
         * @return the permissions that the current user has on the file.
1811
         */
1812
        public EnumSet<Permission> getPermissions() {
1813
            return this.permissions;
1✔
1814
        }
1815

1816
        /**
1817
         * Gets the extension suffix of the file, excluding the dot.
1818
         *
1819
         * @return the extension of the file.
1820
         */
1821
        public String getExtension() {
1822
            return this.extension;
×
1823
        }
1824

1825
        /**
1826
         * Gets whether or not the file is an OSX package.
1827
         *
1828
         * @return true if the file is an OSX package; otherwise false.
1829
         */
1830
        public boolean getIsPackage() {
1831
            return this.isPackage;
×
1832
        }
1833

1834
        /**
1835
         * Gets the current version details of the file.
1836
         *
1837
         * @return the current version details of the file.
1838
         */
1839
        public BoxFileVersion getVersion() {
1840
            return this.version;
1✔
1841
        }
1842

1843
        /**
1844
         * Gets the current expiring preview link.
1845
         *
1846
         * @return the expiring preview link
1847
         */
1848
        public URL getPreviewLink() {
1849
            return this.previewLink;
×
1850
        }
1851

1852
        /**
1853
         * Gets flag indicating whether this file is Watermarked.
1854
         *
1855
         * @return whether the file is watermarked or not
1856
         */
1857
        public boolean getIsWatermarked() {
1858
            return this.isWatermarked;
×
1859
        }
1860

1861
        /**
1862
         * Returns the allowed invitee roles for this file item.
1863
         *
1864
         * @return the list of roles allowed for invited collaborators.
1865
         */
1866
        public List<String> getAllowedInviteeRoles() {
1867
            return this.allowedInviteeRoles;
1✔
1868
        }
1869

1870
        /**
1871
         * Returns the indicator for whether this file item has collaborations.
1872
         *
1873
         * @return indicator for whether this file item has collaborations.
1874
         */
1875
        public Boolean getHasCollaborations() {
1876
            return this.hasCollaborations;
1✔
1877
        }
1878

1879
        /**
1880
         * Gets the metadata on this file associated with a specified scope and template.
1881
         * Makes an attempt to get metadata that was retrieved using getInfo(String ...) method.
1882
         *
1883
         * @param templateName the metadata template type name.
1884
         * @param scope        the scope of the template (usually "global" or "enterprise").
1885
         * @return the metadata returned from the server.
1886
         */
1887
        public Metadata getMetadata(String templateName, String scope) {
1888
            try {
1889
                return this.metadataMap.get(scope).get(templateName);
1✔
1890
            } catch (NullPointerException e) {
×
1891
                return null;
×
1892
            }
1893
        }
1894

1895
        /**
1896
         * Returns the field for indicating whether a file is owned by a user outside the enterprise.
1897
         *
1898
         * @return indicator for whether or not the file is owned by a user outside the enterprise.
1899
         */
1900
        public boolean getIsExternallyOwned() {
1901
            return this.isExternallyOwned;
1✔
1902
        }
1903

1904
        /**
1905
         * Get file's representations.
1906
         *
1907
         * @return list of representations
1908
         */
1909
        public List<Representation> getRepresentations() {
1910
            return this.representations;
1✔
1911
        }
1912

1913
        /**
1914
         * Returns user's name at the time of upload.
1915
         *
1916
         * @return user's name at the time of upload
1917
         */
1918
        public String getUploaderDisplayName() {
1919
            return this.uploaderDisplayName;
1✔
1920
        }
1921

1922
        /**
1923
         * Gets the metadata classification type of this file.
1924
         *
1925
         * @return the metadata classification type of this file.
1926
         */
1927
        public BoxClassification getClassification() {
1928
            return this.classification;
1✔
1929
        }
1930

1931
        /**
1932
         * Returns the retention expiration timestamp for the given file.
1933
         *
1934
         * @return Date representing expiration timestamp
1935
         */
1936
        public Date getDispositionAt() {
1937
            return dispositionAt;
1✔
1938
        }
1939

1940
        /**
1941
         * Modifies the retention expiration timestamp for the given file.
1942
         * This date cannot be shortened once set on a file.
1943
         *
1944
         * @param dispositionAt Date representing expiration timestamp
1945
         */
1946
        public void setDispositionAt(Date dispositionAt) {
1947
            this.dispositionAt = dispositionAt;
1✔
1948
            this.addPendingChange("disposition_at", BoxDateFormat.format(dispositionAt));
1✔
1949
        }
1✔
1950

1951
        /**
1952
         * Returns the flag indicating whether the file is accessible via a shared link.
1953
         *
1954
         * @return boolean flag indicating whether the file is accessible via a shared link.
1955
         */
1956
        public boolean getIsAccessibleViaSharedLink() {
1957
            return this.isAccessibleViaSharedLink;
1✔
1958
        }
1959

1960
        @Override
1961
        protected void parseJSONMember(JsonObject.Member member) {
1962
            super.parseJSONMember(member);
1✔
1963

1964
            String memberName = member.getName();
1✔
1965
            JsonValue value = member.getValue();
1✔
1966
            try {
1967
                switch (memberName) {
1✔
1968
                    case "sha1":
1969
                        this.sha1 = value.asString();
1✔
1970
                        break;
1✔
1971
                    case "version_number":
1972
                        this.versionNumber = value.asString();
1✔
1973
                        break;
1✔
1974
                    case "comment_count":
1975
                        this.commentCount = value.asLong();
1✔
1976
                        break;
1✔
1977
                    case "permissions":
1978
                        this.permissions = this.parsePermissions(value.asObject());
1✔
1979
                        break;
1✔
1980
                    case "extension":
1981
                        this.extension = value.asString();
1✔
1982
                        break;
1✔
1983
                    case "is_package":
1984
                        this.isPackage = value.asBoolean();
1✔
1985
                        break;
1✔
1986
                    case "has_collaborations":
1987
                        this.hasCollaborations = value.asBoolean();
1✔
1988
                        break;
1✔
1989
                    case "is_externally_owned":
1990
                        this.isExternallyOwned = value.asBoolean();
1✔
1991
                        break;
1✔
1992
                    case "file_version":
1993
                        this.version = this.parseFileVersion(value.asObject());
1✔
1994
                        break;
1✔
1995
                    case "allowed_invitee_roles":
1996
                        this.allowedInviteeRoles = this.parseAllowedInviteeRoles(value.asArray());
1✔
1997
                        break;
1✔
1998
                    case "expiring_embed_link":
1999
                        try {
2000
                            String urlString = member.getValue().asObject().get("url").asString();
1✔
2001
                            this.previewLink = new URL(urlString);
1✔
2002
                        } catch (MalformedURLException e) {
×
2003
                            throw new BoxAPIException("Couldn't parse expiring_embed_link/url for file", e);
×
2004
                        }
1✔
2005
                        break;
2006
                    case "lock":
2007
                        if (value.isNull()) {
1✔
2008
                            this.lock = null;
×
2009
                        } else {
2010
                            this.lock = new BoxLock(value.asObject(), BoxFile.this.getAPI());
1✔
2011
                        }
2012
                        break;
1✔
2013
                    case "watermark_info":
2014
                        this.isWatermarked = value.asObject().get("is_watermarked").asBoolean();
1✔
2015
                        break;
1✔
2016
                    case "metadata":
2017
                        this.metadataMap = Parsers.parseAndPopulateMetadataMap(value.asObject());
1✔
2018
                        break;
1✔
2019
                    case "representations":
2020
                        this.representations = Parsers.parseRepresentations(value.asObject());
1✔
2021
                        break;
1✔
2022
                    case "uploader_display_name":
2023
                        this.uploaderDisplayName = value.asString();
1✔
2024
                        break;
1✔
2025
                    case "classification":
2026
                        if (value.isNull()) {
1✔
2027
                            this.classification = null;
×
2028
                        } else {
2029
                            this.classification = new BoxClassification(value.asObject());
1✔
2030
                        }
2031
                        break;
1✔
2032
                    case "disposition_at":
2033
                        this.dispositionAt = BoxDateFormat.parse(value.asString());
1✔
2034
                        break;
1✔
2035
                    case "is_accessible_via_shared_link":
2036
                        this.isAccessibleViaSharedLink = value.asBoolean();
1✔
2037
                        break;
1✔
2038
                    default:
2039
                        break;
2040
                }
2041
            } catch (Exception e) {
×
2042
                throw new BoxDeserializationException(memberName, value.toString(), e);
×
2043
            }
1✔
2044
        }
1✔
2045

2046
        @SuppressWarnings("checkstyle:MissingSwitchDefault")
2047
        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
2048
            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
1✔
2049
            for (JsonObject.Member member : jsonObject) {
1✔
2050
                JsonValue value = member.getValue();
1✔
2051
                if (value.isNull() || !value.asBoolean()) {
1✔
2052
                    continue;
×
2053
                }
2054
                try {
2055
                    permissions.add(Permission.fromJSONValue(member.getName()));
1✔
2056
                } catch (IllegalArgumentException ignored) {
×
2057
                    // If the permission is not recognized, we ignore it.
2058
                }
1✔
2059
            }
1✔
2060

2061
            return permissions;
1✔
2062
        }
2063

2064
        private BoxFileVersion parseFileVersion(JsonObject jsonObject) {
2065
            return new BoxFileVersion(BoxFile.this.getAPI(), jsonObject, BoxFile.this.getID());
1✔
2066
        }
2067

2068
        private List<String> parseAllowedInviteeRoles(JsonArray jsonArray) {
2069
            List<String> roles = new ArrayList<>(jsonArray.size());
1✔
2070
            for (JsonValue value : jsonArray) {
1✔
2071
                roles.add(value.asString());
1✔
2072
            }
1✔
2073

2074
            return roles;
1✔
2075
        }
2076
    }
2077

2078
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc