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

box / box-java-sdk / #4283

10 Dec 2024 10:58AM UTC coverage: 71.848% (+0.1%) from 71.724%
#4283

Pull #1282

github

web-flow
Merge 6d885b346 into d9564e2e8
Pull Request #1282: feat: Support downloading file from shared link

18 of 23 new or added lines in 2 files covered. (78.26%)

1 existing line in 1 file now uncovered.

8103 of 11278 relevant lines covered (71.85%)

0.72 hits per line

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

75.65
/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, "download_url");
1✔
363
        URL url;
364
        try {
365
            url = new URL(item.getDownloadUrl());
1✔
NEW
366
        } catch (MalformedURLException e) {
×
NEW
367
            throw new RuntimeException(
×
NEW
368
                    String.format("Invalid download URL %s for shared link %s", item.getDownloadUrl(), sharedLink), e
×
369
            );
370
        }
1✔
371
        BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
1✔
372
        BoxAPIResponse response = request.send();
1✔
373
        writeStream(response, output, listener);
1✔
374
    }
1✔
375

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

559
        this.getRepresentationContent(representationHint, "", output);
1✔
560
    }
1✔
561

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

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

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

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

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

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

633
    }
1✔
634

635
    private String pollRepInfo(URL infoURL) {
636

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

641
            Representation rep = new Representation(response);
1✔
642

643
            String repState = rep.getStatus().getState();
1✔
644

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

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

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

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

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

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

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

742

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

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

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

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

773
            return versions;
1✔
774
        }
775
    }
776

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

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

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

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

804
        preflightInfo.add("size", fileSize);
×
805

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

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

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

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

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

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

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

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

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

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

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

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

940
        request.putField("attributes", attributesJSON.toString());
1✔
941

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

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

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

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

969
        return info.getPreviewLink();
×
970
    }
971

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

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

993
            return comments;
1✔
994
        }
995
    }
996

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

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

1023
            return tasks;
1✔
1024
        }
1025
    }
1026

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

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

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

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

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

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

1122
        return classification.getString(Metadata.CLASSIFICATION_KEY);
1✔
1123
    }
1124

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

1136
        return classification.getString(Metadata.CLASSIFICATION_KEY);
1✔
1137
    }
1138

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

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

1161
        return classification.getString(Metadata.CLASSIFICATION_KEY);
1✔
1162
    }
1163

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

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

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

1185
        return metadata.getString(Metadata.CLASSIFICATION_KEY);
1✔
1186
    }
1187

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

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

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

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

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

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

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

1247
        try (BoxJSONResponse response = request.send()) {
1✔
1248

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

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

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

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

1268
        request.setBody(lockObject.toString());
1✔
1269
        request.send().close();
1✔
1270
    }
1✔
1271

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1622
    }
1623

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

1633
        /**
1634
         * JPG image format.
1635
         */
1636
        JPG
×
1637
    }
1638

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

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

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

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

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

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

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

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

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

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

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

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

1704
        private final String jsonValue;
1705

1706
        Permission(String jsonValue) {
1✔
1707
            this.jsonValue = jsonValue;
1✔
1708
        }
1✔
1709

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

1714
        String toJSONValue() {
1715
            return this.jsonValue;
×
1716
        }
1717
    }
1718

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2063
            return permissions;
1✔
2064
        }
2065

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

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

2076
            return roles;
1✔
2077
        }
2078
    }
2079

2080
}
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

© 2025 Coveralls, Inc