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

box / box-java-sdk / #4058

30 Oct 2024 03:25PM UTC coverage: 71.734% (-0.01%) from 71.747%
#4058

push

github

web-flow
feat: Expose `getVersionByID` method on `BoxFile` (#1268)

8 of 12 new or added lines in 1 file covered. (66.67%)

1 existing line in 1 file now uncovered.

8045 of 11215 relevant lines covered (71.73%)

0.72 hits per line

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

75.84
/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.http.ContentType.APPLICATION_JSON;
5
import static com.box.sdk.http.ContentType.APPLICATION_JSON_PATCH;
6
import static com.eclipsesource.json.Json.NULL;
7

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

32

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

314
    /**
315
     * Downloads a part of this file's contents, starting at specified byte offset.
316
     *
317
     * @param output the stream to where the file will be written.
318
     * @param offset the byte offset at which to start the download.
319
     */
320
    public void downloadRange(OutputStream output, long offset) {
321
        this.downloadRange(output, offset, -1);
×
322
    }
×
323

324
    /**
325
     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd.
326
     *
327
     * @param output     the stream to where the file will be written.
328
     * @param rangeStart the byte offset at which to start the download.
329
     * @param rangeEnd   the byte offset at which to stop the download.
330
     */
331
    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd) {
332
        this.downloadRange(output, rangeStart, rangeEnd, null);
×
333
    }
×
334

335
    /**
336
     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd, while reporting the
337
     * progress to a ProgressListener.
338
     *
339
     * @param output     the stream to where the file will be written.
340
     * @param rangeStart the byte offset at which to start the download.
341
     * @param rangeEnd   the byte offset at which to stop the download.
342
     * @param listener   a listener for monitoring the download's progress.
343
     */
344
    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd, ProgressListener listener) {
345
        URL url = getDownloadUrl();
×
346
        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
×
347
        if (rangeEnd > 0) {
×
348
            request.addHeader("Range", String.format("bytes=%s-%s", rangeStart, rangeEnd));
×
349
        } else {
350
            request.addHeader("Range", String.format("bytes=%s-", rangeStart));
×
351
        }
352
        writeStream(request.send(), output, listener);
×
353
    }
×
354

355
    /**
356
     * Can be used to override the URL used for file download.
357
     *
358
     * @return URL for file downalod
359
     */
360
    protected URL getDownloadUrl() {
361
        return CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
×
362
    }
363

364
    @Override
365
    public BoxFile.Info copy(BoxFolder destination) {
366
        return this.copy(destination, null);
1✔
367
    }
368

369
    @Override
370
    public BoxFile.Info copy(BoxFolder destination, String newName) {
371
        URL url = COPY_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
372

373
        JsonObject parent = new JsonObject();
1✔
374
        parent.add("id", destination.getID());
1✔
375

376
        JsonObject copyInfo = new JsonObject();
1✔
377
        copyInfo.add("parent", parent);
1✔
378
        if (newName != null) {
1✔
379
            copyInfo.add("name", newName);
×
380
        }
381

382
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1✔
383
        request.setBody(copyInfo.toString());
1✔
384
        try (BoxJSONResponse response = request.send()) {
1✔
385
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1✔
386
            BoxFile copiedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
1✔
387
            return copiedFile.new Info(responseJSON);
1✔
388
        }
389
    }
390

391
    /**
392
     * Deletes this file by moving it to the trash.
393
     */
394
    public void delete() {
395
        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
396
        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
1✔
397
        request.send().close();
1✔
398
    }
1✔
399

400
    @Override
401
    public BoxItem.Info move(BoxFolder destination) {
402
        return this.move(destination, null);
1✔
403
    }
404

405
    @Override
406
    public BoxItem.Info move(BoxFolder destination, String newName) {
407
        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
408
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
1✔
409

410
        JsonObject parent = new JsonObject();
1✔
411
        parent.add("id", destination.getID());
1✔
412

413
        JsonObject updateInfo = new JsonObject();
1✔
414
        updateInfo.add("parent", parent);
1✔
415
        if (newName != null) {
1✔
416
            updateInfo.add("name", newName);
×
417
        }
418

419
        request.setBody(updateInfo.toString());
1✔
420
        try (BoxJSONResponse response = request.send()) {
1✔
421
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1✔
422
            BoxFile movedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
1✔
423
            return movedFile.new Info(responseJSON);
1✔
424
        }
425
    }
426

427
    /**
428
     * Renames this file.
429
     *
430
     * @param newName the new name of the file.
431
     */
432
    public void rename(String newName) {
433
        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
434
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
1✔
435

436
        JsonObject updateInfo = new JsonObject();
1✔
437
        updateInfo.add("name", newName);
1✔
438

439
        request.setBody(updateInfo.toString());
1✔
440
        try (BoxJSONResponse response = request.send()) {
1✔
441
            response.getJSON();
1✔
442
        }
443
    }
1✔
444

445
    @Override
446
    public BoxFile.Info getInfo(String... fields) {
447
        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
448
        if (fields.length > 0) {
1✔
449
            String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
×
450
            url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
×
451
        }
452

453
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
454
        try (BoxJSONResponse response = request.send()) {
1✔
455
            return new Info(response.getJSON());
1✔
456
        }
457
    }
458

459
    /**
460
     * Gets information about this item including a specified set of representations.
461
     *
462
     * @param representationHints hints for representations to be retrieved
463
     * @param fields              the fields to retrieve.
464
     * @return info about this item containing only the specified fields, including representations.
465
     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
466
     */
467
    public BoxFile.Info getInfoWithRepresentations(String representationHints, String... fields) {
468
        if (representationHints.matches(Representation.X_REP_HINTS_PATTERN)) {
1✔
469
            //Since the user intends to get representations, add it to fields, even if user has missed it
470
            Set<String> fieldsSet = new HashSet<>(Arrays.asList(fields));
1✔
471
            fieldsSet.add("representations");
1✔
472
            String queryString = new QueryStringBuilder().appendParam("fields",
1✔
473
                fieldsSet.toArray(new String[0])).toString();
1✔
474
            URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1✔
475

476
            BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
477
            request.addHeader("X-Rep-Hints", representationHints);
1✔
478
            try (BoxJSONResponse response = request.send()) {
1✔
479
                return new Info(response.getJSON());
1✔
480
            }
481
        } else {
482
            throw new BoxAPIException(
×
483
                "Represention hints is not valid. Refer documention on how to construct X-Rep-Hints Header"
484
            );
485
        }
486
    }
487

488
    /**
489
     * Fetches the contents of a file representation and writes them to the provided output stream.
490
     *
491
     * @param representationHint the X-Rep-Hints query for the representation to fetch.
492
     * @param output             the output stream to write the contents to.
493
     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
494
     */
495
    public void getRepresentationContent(String representationHint, OutputStream output) {
496

497
        this.getRepresentationContent(representationHint, "", output);
1✔
498
    }
1✔
499

500
    /**
501
     * Fetches the contents of a file representation with asset path and writes them to the provided output stream.
502
     *
503
     * @param representationHint the X-Rep-Hints query for the representation to fetch.
504
     * @param assetPath          the path of the asset for representations containing multiple files.
505
     * @param output             the output stream to write the contents to.
506
     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
507
     */
508
    public void getRepresentationContent(String representationHint, String assetPath, OutputStream output) {
509
        this.getRepresentationContent(representationHint, assetPath, output, Integer.MAX_VALUE);
1✔
510
    }
1✔
511

512
    /**
513
     * Fetches the contents of a file representation with asset path and writes them to the provided output stream.
514
     *
515
     * @param representationHint the X-Rep-Hints query for the representation to fetch.
516
     * @param assetPath          the path of the asset for representations containing multiple files.
517
     * @param output             the output stream to write the contents to.
518
     * @param maxRetries         the maximum number of attempts to call the request for retrieving status information
519
     *                           indicating whether the representation has been generated and is ready to fetch.
520
     *                           If the number of attempts is exceeded, the method will throw a BoxApiException.
521
     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
522
     */
523
    public void getRepresentationContent(
524
        String representationHint, String assetPath, OutputStream output, int maxRetries
525
    ) {
526
        List<Representation> reps = this.getInfoWithRepresentations(representationHint).getRepresentations();
1✔
527
        if (reps.size() < 1) {
1✔
528
            throw new BoxAPIException("No matching representations found for requested '" + representationHint
×
529
                + "' hint");
530
        }
531
        Representation representation = reps.get(0);
1✔
532
        String repState = representation.getStatus().getState();
1✔
533

534
        switch (repState) {
1✔
535
            case "viewable":
536
            case "success":
537
                this.makeRepresentationContentRequest(representation.getContent().getUrlTemplate(), assetPath, output);
×
538
                break;
×
539
            case "pending":
540
            case "none":
541

542
                String repContentURLString = null;
1✔
543
                int attemptNumber = 0;
1✔
544
                while (repContentURLString == null && attemptNumber < maxRetries) {
1✔
545
                    repContentURLString = this.pollRepInfo(representation.getInfo().getUrl());
1✔
546
                    try {
547
                        Thread.sleep(100);
1✔
548
                    } catch (InterruptedException e) {
×
549
                        throw new RuntimeException(e);
×
550
                    }
1✔
551
                    attemptNumber++;
1✔
552
                }
553

554
                if (repContentURLString != null) {
1✔
555
                    this.makeRepresentationContentRequest(repContentURLString, assetPath, output);
1✔
556
                } else {
557
                    throw new BoxAPIException(
1✔
558
                        "Representation did not have a success status allowing it to be retrieved after "
559
                            + maxRetries
560
                            + " attempts"
561
                    );
562
                }
563

564
                break;
565
            case "error":
566
                throw new BoxAPIException("Representation had error status");
×
567
            default:
568
                throw new BoxAPIException("Representation had unknown status");
×
569
        }
570

571
    }
1✔
572

573
    private String pollRepInfo(URL infoURL) {
574

575
        BoxJSONRequest infoRequest = new BoxJSONRequest(this.getAPI(), infoURL, HttpMethod.GET);
1✔
576
        try (BoxJSONResponse infoResponse = infoRequest.send()) {
1✔
577
            JsonObject response = infoResponse.getJsonObject();
1✔
578

579
            Representation rep = new Representation(response);
1✔
580

581
            String repState = rep.getStatus().getState();
1✔
582

583
            switch (repState) {
1✔
584
                case "viewable":
585
                case "success":
586
                    return rep.getContent().getUrlTemplate();
1✔
587
                case "pending":
588
                case "none":
589
                    return null;
1✔
590
                case "error":
591
                    throw new BoxAPIException("Representation had error status");
×
592
                default:
593
                    throw new BoxAPIException("Representation had unknown status");
×
594
            }
595
        }
596
    }
597

598
    private void makeRepresentationContentRequest(
599
        String representationURLTemplate, String assetPath, OutputStream output
600
    ) {
601
        try {
602
            URL repURL = new URL(representationURLTemplate.replace("{+asset_path}", assetPath));
1✔
603
            BoxAPIRequest repContentReq = new BoxAPIRequest(this.getAPI(), repURL, HttpMethod.GET);
1✔
604
            BoxAPIResponse response = repContentReq.send();
1✔
605
            writeStream(response, output);
1✔
606
        } catch (MalformedURLException ex) {
×
607

608
            throw new BoxAPIException("Could not generate representation content URL");
×
609
        }
1✔
610
    }
1✔
611

612
    /**
613
     * Updates the information about this file with any info fields that have been modified locally.
614
     *
615
     * <p>The only fields that will be updated are the ones that have been modified locally. For example, the following
616
     * code won't update any information (or even send a network request) since none of the info's fields were
617
     * changed:</p>
618
     *
619
     * <pre>BoxFile file = new File(api, id);
620
     * BoxFile.Info info = file.getInfo();
621
     * file.updateInfo(info);</pre>
622
     *
623
     * @param info the updated info.
624
     */
625
    public void updateInfo(BoxFile.Info info) {
626
        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
627
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
1✔
628
        request.setBody(info.getPendingChanges());
1✔
629
        try (BoxJSONResponse response = request.send()) {
1✔
630
            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
1✔
631
            info.update(jsonObject);
1✔
632
        }
633
    }
1✔
634

635
    /**
636
     * Retrieve a specific file version.
637
     *
638
     * @param fileVersionID the ID of the file version to retrieve.
639
     * @param fields        the optional fields to retrieve.
640
     * @return a specific file version.
641
     */
642
    public BoxFileVersion getVersionByID(String fileVersionID, String... fields) {
643
        URL url = BoxFileVersion.VERSION_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), fileVersionID);
1✔
644
        if (fields.length > 0) {
1✔
NEW
645
            String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
×
NEW
646
            url = BoxFileVersion.VERSION_URL_TEMPLATE.buildWithQuery(
×
NEW
647
                this.getAPI().getBaseURL(),
×
648
                queryString,
NEW
649
                this.getID(),
×
650
                fileVersionID
651
            );
652
        }
653

654
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
655
        try (BoxJSONResponse response = request.send()) {
1✔
656
            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
1✔
657
            return new BoxFileVersion(this.getAPI(), jsonObject, this.getID());
1✔
658
        }
659
    }
660

661
    /**
662
     * Gets up to 1000 versions of this file. Note that only users with premium accounts will be able to retrieve
663
     * previous versions of their files. `fields` parameter is optional, if specified only requested fields will
664
     * be returned:
665
     * <pre>
666
     * {@code
667
     * new BoxFile(api, file_id).getVersions()       // will return all default fields
668
     * new BoxFile(api, file_id).getVersions("name") // will return only specified fields
669
     * }
670
     * </pre>
671
     *
672
     * @param fields the fields to retrieve. If nothing provided default fields will be returned.
673
     *               You can find list of available fields at {@link BoxFile#ALL_VERSION_FIELDS}
674
     * @return a list of previous file versions.
675
     */
676
    public Collection<BoxFileVersion> getVersions(String... fields) {
677
        return getVersionsRange(0, BoxFileVersion.DEFAULT_LIMIT, fields);
1✔
678
    }
679

680

681
    /**
682
     * Retrieves a specific range of versions of this file.
683
     *
684
     * @param offset the index of the first version of this file to retrieve.
685
     * @param limit  the maximum number of versions to retrieve after the offset.
686
     * @param fields the fields to retrieve.
687
     * @return a partial collection containing the specified range of versions of this file.
688
     */
689
    public PartialCollection<BoxFileVersion> getVersionsRange(long offset, long limit, String... fields) {
690
        QueryStringBuilder builder = new QueryStringBuilder()
1✔
691
            .appendParam("limit", limit)
1✔
692
            .appendParam("offset", offset);
1✔
693

694
        if (fields.length > 0) {
1✔
695
            builder.appendParam("fields", fields);
1✔
696
        }
697

698
        URL url = VERSIONS_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID());
1✔
699
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
700
        try (BoxJSONResponse response = request.send()) {
1✔
701

702
            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
1✔
703
            String totalCountString = jsonObject.get("total_count").toString();
1✔
704
            long fullSize = Double.valueOf(totalCountString).longValue();
1✔
705
            PartialCollection<BoxFileVersion> versions = new PartialCollection<>(offset, limit, fullSize);
1✔
706
            JsonArray entries = jsonObject.get("entries").asArray();
1✔
707
            for (JsonValue entry : entries) {
1✔
708
                versions.add(new BoxFileVersion(this.getAPI(), entry.asObject(), this.getID()));
1✔
709
            }
1✔
710

711
            return versions;
1✔
712
        }
713
    }
714

715
    /**
716
     * Checks if a new version of the file can be uploaded with the specified name.
717
     *
718
     * @param name the new name for the file.
719
     * @return whether or not the file version can be uploaded.
720
     */
721
    public boolean canUploadVersion(String name) {
722
        return this.canUploadVersion(name, 0);
×
723
    }
724

725
    /**
726
     * Checks if a new version of the file can be uploaded with the specified name and size.
727
     *
728
     * @param name     the new name for the file.
729
     * @param fileSize the size of the new version content in bytes.
730
     * @return whether the file version can be uploaded.
731
     */
732
    public boolean canUploadVersion(String name, long fileSize) {
733

734
        URL url = getDownloadUrl();
×
735
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
×
736

737
        JsonObject preflightInfo = new JsonObject();
×
738
        if (name != null) {
×
739
            preflightInfo.add("name", name);
×
740
        }
741

742
        preflightInfo.add("size", fileSize);
×
743

744
        request.setBody(preflightInfo.toString());
×
745
        try (BoxAPIResponse response = request.send()) {
×
746
            return response.getResponseCode() == 200;
×
747
        } catch (BoxAPIException ex) {
×
748
            if (ex.getResponseCode() >= 400 && ex.getResponseCode() < 500) {
×
749
                // This looks like an error response, meaning the upload would fail
750
                return false;
×
751
            } else {
752
                // This looks like a network error or server error, rethrow exception
753
                throw ex;
×
754
            }
755
        }
756
    }
757

758
    /**
759
     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
760
     * will be able to view and recover previous versions of the file.
761
     *
762
     * @param fileContent a stream containing the new file contents.
763
     * @return the uploaded file version.
764
     */
765
    public BoxFile.Info uploadNewVersion(InputStream fileContent) {
766
        return this.uploadNewVersion(fileContent, null);
1✔
767
    }
768

769
    /**
770
     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
771
     * will be able to view and recover previous versions of the file.
772
     *
773
     * @param fileContent     a stream containing the new file contents.
774
     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
775
     * @return the uploaded file version.
776
     */
777
    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1) {
778
        return this.uploadNewVersion(fileContent, fileContentSHA1, null);
1✔
779
    }
780

781
    /**
782
     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
783
     * will be able to view and recover previous versions of the file.
784
     *
785
     * @param fileContent     a stream containing the new file contents.
786
     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
787
     * @param modified        the date that the new version was modified.
788
     * @return the uploaded file version.
789
     */
790
    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified) {
791
        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, 0, null);
1✔
792
    }
793

794
    /**
795
     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
796
     * will be able to view and recover previous versions of the file.
797
     *
798
     * @param fileContent     a stream containing the new file contents.
799
     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
800
     * @param modified        the date that the new version was modified.
801
     * @param name            the new name for the file
802
     * @return the uploaded file version.
803
     */
804
    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, String name) {
805
        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, name, 0, null);
×
806
    }
807

808
    /**
809
     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
810
     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
811
     * of the file.
812
     *
813
     * @param fileContent a stream containing the new file contents.
814
     * @param modified    the date that the new version was modified.
815
     * @param fileSize    the size of the file used for determining the progress of the upload.
816
     * @param listener    a listener for monitoring the upload's progress.
817
     * @return the uploaded file version.
818
     */
819
    public BoxFile.Info uploadNewVersion(InputStream fileContent, Date modified, long fileSize,
820
                                         ProgressListener listener) {
821
        return this.uploadNewVersion(fileContent, null, modified, fileSize, listener);
×
822
    }
823

824
    /**
825
     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
826
     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
827
     * of the file.
828
     *
829
     * @param fileContent     a stream containing the new file contents.
830
     * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header
831
     * @param modified        the date that the new version was modified.
832
     * @param fileSize        the size of the file used for determining the progress of the upload.
833
     * @param listener        a listener for monitoring the upload's progress.
834
     * @return the uploaded file version.
835
     */
836
    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, long fileSize,
837
                                         ProgressListener listener) {
838
        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, null, fileSize, listener);
1✔
839
    }
840

841
    /**
842
     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
843
     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
844
     * of the file.
845
     *
846
     * @param fileContent     a stream containing the new file contents.
847
     * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header
848
     * @param modified        the date that the new version was modified.
849
     * @param name            the new name for the file
850
     * @param fileSize        the size of the file used for determining the progress of the upload.
851
     * @param listener        a listener for monitoring the upload's progress.
852
     * @return the uploaded file version.
853
     */
854
    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, String name,
855
                                         long fileSize, ProgressListener listener) {
856
        URL uploadURL = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1✔
857
        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
1✔
858

859
        if (fileSize > 0) {
1✔
860
            request.setFile(fileContent, "", fileSize);
×
861
        } else {
862
            request.setFile(fileContent, "");
1✔
863
        }
864

865
        if (fileContentSHA1 != null) {
1✔
866
            request.setContentSHA1(fileContentSHA1);
×
867
        }
868

869
        JsonObject attributesJSON = new JsonObject();
1✔
870
        if (modified != null) {
1✔
871
            attributesJSON.add("content_modified_at", BoxDateFormat.format(modified));
×
872
        }
873

874
        if (name != null) {
1✔
875
            attributesJSON.add("name", name);
×
876
        }
877

878
        request.putField("attributes", attributesJSON.toString());
1✔
879

880
        BoxJSONResponse response = null;
1✔
881
        try {
882
            if (listener == null) {
1✔
883
                // upload is multipart request but response is JSON
884
                response = (BoxJSONResponse) request.send();
1✔
885
            } else {
886
                // upload is multipart request but response is JSON
887
                response = (BoxJSONResponse) request.send(listener);
×
888
            }
889

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

892
            return new BoxFile.Info(fileJSON);
1✔
893
        } finally {
894
            Optional.ofNullable(response).ifPresent(BoxAPIResponse::close);
1✔
895
        }
896
    }
897

898
    /**
899
     * Gets an expiring URL for creating an embedded preview session. The URL will expire after 60 seconds and the
900
     * preview session will expire after 60 minutes.
901
     *
902
     * @return the expiring preview link
903
     */
904
    public URL getPreviewLink() {
905
        BoxFile.Info info = this.getInfo("expiring_embed_link");
×
906

907
        return info.getPreviewLink();
×
908
    }
909

910
    /**
911
     * Gets a list of any comments on this file.
912
     *
913
     * @return a list of comments on this file.
914
     */
915
    public List<BoxComment.Info> getComments() {
916
        URL url = GET_COMMENTS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1✔
917
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
918
        try (BoxJSONResponse response = request.send()) {
1✔
919
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1✔
920

921
            int totalCount = responseJSON.get("total_count").asInt();
1✔
922
            List<BoxComment.Info> comments = new ArrayList<>(totalCount);
1✔
923
            JsonArray entries = responseJSON.get("entries").asArray();
1✔
924
            for (JsonValue value : entries) {
1✔
925
                JsonObject commentJSON = value.asObject();
1✔
926
                BoxComment comment = new BoxComment(this.getAPI(), commentJSON.get("id").asString());
1✔
927
                BoxComment.Info info = comment.new Info(commentJSON);
1✔
928
                comments.add(info);
1✔
929
            }
1✔
930

931
            return comments;
1✔
932
        }
933
    }
934

935
    /**
936
     * Gets a list of any tasks on this file with requested fields.
937
     *
938
     * @param fields optional fields to retrieve for this task.
939
     * @return a list of tasks on this file.
940
     */
941
    public List<BoxTask.Info> getTasks(String... fields) {
942
        QueryStringBuilder builder = new QueryStringBuilder();
1✔
943
        if (fields.length > 0) {
1✔
944
            builder.appendParam("fields", fields);
1✔
945
        }
946
        URL url = GET_TASKS_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID());
1✔
947
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
948
        try (BoxJSONResponse response = request.send()) {
1✔
949
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1✔
950

951
            int totalCount = responseJSON.get("total_count").asInt();
1✔
952
            List<BoxTask.Info> tasks = new ArrayList<>(totalCount);
1✔
953
            JsonArray entries = responseJSON.get("entries").asArray();
1✔
954
            for (JsonValue value : entries) {
1✔
955
                JsonObject taskJSON = value.asObject();
1✔
956
                BoxTask task = new BoxTask(this.getAPI(), taskJSON.get("id").asString());
1✔
957
                BoxTask.Info info = task.new Info(taskJSON);
1✔
958
                tasks.add(info);
1✔
959
            }
1✔
960

961
            return tasks;
1✔
962
        }
963
    }
964

965
    /**
966
     * Creates metadata on this file in the global properties template.
967
     *
968
     * @param metadata The new metadata values.
969
     * @return the metadata returned from the server.
970
     */
971
    public Metadata createMetadata(Metadata metadata) {
972
        return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata);
1✔
973
    }
974

975
    /**
976
     * Creates metadata on this file in the specified template type.
977
     *
978
     * @param typeName the metadata template type name.
979
     * @param metadata the new metadata values.
980
     * @return the metadata returned from the server.
981
     */
982
    public Metadata createMetadata(String typeName, Metadata metadata) {
983
        String scope = Metadata.scopeBasedOnType(typeName);
1✔
984
        return this.createMetadata(typeName, scope, metadata);
1✔
985
    }
986

987
    /**
988
     * Creates metadata on this file in the specified template type.
989
     *
990
     * @param typeName the metadata template type name.
991
     * @param scope    the metadata scope (global or enterprise).
992
     * @param metadata the new metadata values.
993
     * @return the metadata returned from the server.
994
     */
995
    public Metadata createMetadata(String typeName, String scope, Metadata metadata) {
996
        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1✔
997
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1✔
998
        request.setBody(metadata.toString());
1✔
999
        try (BoxJSONResponse response = request.send()) {
1✔
1000
            return new Metadata(Json.parse(response.getJSON()).asObject());
1✔
1001
        }
1002
    }
1003

1004
    /**
1005
     * Sets the provided metadata on the file. If metadata has already been created on this file,
1006
     * it overwrites metadata keys specified in the `metadata` param.
1007
     *
1008
     * @param templateName the name of the metadata template.
1009
     * @param scope        the scope of the template (usually "global" or "enterprise").
1010
     * @param metadata     the new metadata values.
1011
     * @return the metadata returned from the server.
1012
     */
1013
    public Metadata setMetadata(String templateName, String scope, Metadata metadata) {
1014
        try {
1015
            return this.createMetadata(templateName, scope, metadata);
×
1016
        } catch (BoxAPIException e) {
1✔
1017
            if (e.getResponseCode() == 409) {
1✔
1018
                if (metadata.getOperations().isEmpty()) {
1✔
1019
                    return getMetadata();
1✔
1020
                } else {
1021
                    return updateExistingTemplate(templateName, scope, metadata);
1✔
1022
                }
1023
            } else {
1024
                throw e;
×
1025
            }
1026
        }
1027
    }
1028

1029
    private Metadata updateExistingTemplate(String templateName, String scope, Metadata metadata) {
1030
        Metadata metadataToUpdate = new Metadata(scope, templateName);
1✔
1031
        for (JsonValue value : metadata.getOperations()) {
1✔
1032
            if (value.asObject().get("value").isNumber()) {
1✔
1033
                metadataToUpdate.add(value.asObject().get("path").asString(),
1✔
1034
                    value.asObject().get("value").asDouble());
1✔
1035
            } else if (value.asObject().get("value").isString()) {
1✔
1036
                metadataToUpdate.add(value.asObject().get("path").asString(),
1✔
1037
                    value.asObject().get("value").asString());
1✔
1038
            } else if (value.asObject().get("value").isArray()) {
1✔
1039
                ArrayList<String> list = new ArrayList<>();
1✔
1040
                for (JsonValue jsonValue : value.asObject().get("value").asArray()) {
1✔
1041
                    list.add(jsonValue.asString());
1✔
1042
                }
1✔
1043
                metadataToUpdate.add(value.asObject().get("path").asString(), list);
1✔
1044
            }
1045
        }
1✔
1046
        return this.updateMetadata(metadataToUpdate);
1✔
1047
    }
1048

1049
    /**
1050
     * Adds a metadata classification to the specified file.
1051
     *
1052
     * @param classificationType the metadata classification type.
1053
     * @return the metadata classification type added to the file.
1054
     */
1055
    public String addClassification(String classificationType) {
1056
        Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType);
1✔
1057
        Metadata classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY,
1✔
1058
            "enterprise", metadata);
1059

1060
        return classification.getString(Metadata.CLASSIFICATION_KEY);
1✔
1061
    }
1062

1063
    /**
1064
     * Updates a metadata classification on the specified file.
1065
     *
1066
     * @param classificationType the metadata classification type.
1067
     * @return the new metadata classification type updated on the file.
1068
     */
1069
    public String updateClassification(String classificationType) {
1070
        Metadata metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY);
1✔
1071
        metadata.add("/Box__Security__Classification__Key", classificationType);
1✔
1072
        Metadata classification = this.updateMetadata(metadata);
1✔
1073

1074
        return classification.getString(Metadata.CLASSIFICATION_KEY);
1✔
1075
    }
1076

1077
    /**
1078
     * Attempts to add classification to a file. If classification already exists then do update.
1079
     *
1080
     * @param classificationType the metadata classification type.
1081
     * @return the metadata classification type on the file.
1082
     */
1083
    public String setClassification(String classificationType) {
1084
        Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType);
1✔
1085
        Metadata classification;
1086

1087
        try {
1088
            classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise", metadata);
×
1089
        } catch (BoxAPIException e) {
1✔
1090
            if (e.getResponseCode() == 409) {
1✔
1091
                metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY);
1✔
1092
                metadata.replace(Metadata.CLASSIFICATION_KEY, classificationType);
1✔
1093
                classification = this.updateMetadata(metadata);
1✔
1094
            } else {
1095
                throw e;
1✔
1096
            }
1097
        }
×
1098

1099
        return classification.getString(Metadata.CLASSIFICATION_KEY);
1✔
1100
    }
1101

1102
    /**
1103
     * Gets the classification type for the specified file.
1104
     *
1105
     * @return the metadata classification type on the file.
1106
     */
1107
    public String getClassification() {
1108
        Metadata metadata;
1109
        try {
1110
            metadata = this.getMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY);
1✔
1111

1112
        } catch (BoxAPIException e) {
1✔
1113
            JsonObject responseObject = Json.parse(e.getResponse()).asObject();
1✔
1114
            String code = responseObject.get("code").asString();
1✔
1115

1116
            if (e.getResponseCode() == 404 && code.equals("instance_not_found")) {
1✔
1117
                return null;
1✔
1118
            } else {
1119
                throw e;
1✔
1120
            }
1121
        }
1✔
1122

1123
        return metadata.getString(Metadata.CLASSIFICATION_KEY);
1✔
1124
    }
1125

1126
    /**
1127
     * Deletes the classification on the file.
1128
     */
1129
    public void deleteClassification() {
1130
        this.deleteMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise");
1✔
1131
    }
1✔
1132

1133
    /**
1134
     * Locks a file.
1135
     *
1136
     * @return the lock returned from the server.
1137
     */
1138
    public BoxLock lock() {
1139
        return this.lock(null, false);
×
1140
    }
1141

1142
    /**
1143
     * Locks a file.
1144
     *
1145
     * @param isDownloadPrevented is downloading of file prevented when locked.
1146
     * @return the lock returned from the server.
1147
     */
1148
    public BoxLock lock(boolean isDownloadPrevented) {
1149
        return this.lock(null, isDownloadPrevented);
1✔
1150
    }
1151

1152
    /**
1153
     * Locks a file.
1154
     *
1155
     * @param expiresAt expiration date of the lock.
1156
     * @return the lock returned from the server.
1157
     */
1158
    public BoxLock lock(Date expiresAt) {
1159
        return this.lock(expiresAt, false);
×
1160
    }
1161

1162
    /**
1163
     * Locks a file.
1164
     *
1165
     * @param expiresAt           expiration date of the lock.
1166
     * @param isDownloadPrevented is downloading of file prevented when locked.
1167
     * @return the lock returned from the server.
1168
     */
1169
    public BoxLock lock(Date expiresAt, boolean isDownloadPrevented) {
1170
        String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString();
1✔
1171
        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1✔
1172
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
1✔
1173

1174
        JsonObject lockConfig = new JsonObject();
1✔
1175
        lockConfig.add("type", "lock");
1✔
1176
        if (expiresAt != null) {
1✔
1177
            lockConfig.add("expires_at", BoxDateFormat.format(expiresAt));
×
1178
        }
1179
        lockConfig.add("is_download_prevented", isDownloadPrevented);
1✔
1180

1181
        JsonObject requestJSON = new JsonObject();
1✔
1182
        requestJSON.add("lock", lockConfig);
1✔
1183
        request.setBody(requestJSON.toString());
1✔
1184

1185
        try (BoxJSONResponse response = request.send()) {
1✔
1186

1187
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1✔
1188
            JsonValue lockValue = responseJSON.get("lock");
1✔
1189
            JsonObject lockJSON = Json.parse(lockValue.toString()).asObject();
1✔
1190

1191
            return new BoxLock(lockJSON, this.getAPI());
1✔
1192
        }
1193
    }
1194

1195
    /**
1196
     * Unlocks a file.
1197
     */
1198
    public void unlock() {
1199
        String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString();
1✔
1200
        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1✔
1201
        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
1✔
1202

1203
        JsonObject lockObject = new JsonObject();
1✔
1204
        lockObject.add("lock", NULL);
1✔
1205

1206
        request.setBody(lockObject.toString());
1✔
1207
        request.send().close();
1✔
1208
    }
1✔
1209

1210
    /**
1211
     * Used to retrieve all metadata associated with the file.
1212
     *
1213
     * @param fields the optional fields to retrieve.
1214
     * @return An iterable of metadata instances associated with the file.
1215
     */
1216
    public Iterable<Metadata> getAllMetadata(String... fields) {
1217
        return Metadata.getAllMetadata(this, fields);
×
1218
    }
1219

1220
    /**
1221
     * Gets the file properties metadata.
1222
     *
1223
     * @return the metadata returned from the server.
1224
     */
1225
    public Metadata getMetadata() {
1226
        return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE);
1✔
1227
    }
1228

1229
    /**
1230
     * Gets the file metadata of specified template type.
1231
     *
1232
     * @param typeName the metadata template type name.
1233
     * @return the metadata returned from the server.
1234
     */
1235
    public Metadata getMetadata(String typeName) {
1236
        String scope = Metadata.scopeBasedOnType(typeName);
1✔
1237
        return this.getMetadata(typeName, scope);
1✔
1238
    }
1239

1240
    /**
1241
     * Gets the file metadata of specified template type.
1242
     *
1243
     * @param typeName the metadata template type name.
1244
     * @param scope    the metadata scope (global or enterprise).
1245
     * @return the metadata returned from the server.
1246
     */
1247
    public Metadata getMetadata(String typeName, String scope) {
1248
        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1✔
1249
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
1✔
1250
        try (BoxJSONResponse response = request.send()) {
1✔
1251
            return new Metadata(Json.parse(response.getJSON()).asObject());
1✔
1252
        }
1253
    }
1254

1255
    /**
1256
     * Updates the file metadata.
1257
     *
1258
     * @param metadata the new metadata values.
1259
     * @return the metadata returned from the server.
1260
     */
1261
    public Metadata updateMetadata(Metadata metadata) {
1262
        String scope;
1263
        if (metadata.getScope().equals(Metadata.GLOBAL_METADATA_SCOPE)) {
1✔
1264
            scope = Metadata.GLOBAL_METADATA_SCOPE;
×
1265
        } else if (metadata.getScope().startsWith(Metadata.ENTERPRISE_METADATA_SCOPE)) {
1✔
1266
            scope = metadata.getScope();
1✔
1267
        } else {
1268
            scope = Metadata.ENTERPRISE_METADATA_SCOPE;
×
1269
        }
1270

1271
        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(),
1✔
1272
            scope, metadata.getTemplateName());
1✔
1273
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT", APPLICATION_JSON_PATCH);
1✔
1274
        request.setBody(metadata.getPatch());
1✔
1275
        try (BoxJSONResponse response = request.send()) {
1✔
1276
            return new Metadata(Json.parse(response.getJSON()).asObject());
1✔
1277
        }
1278
    }
1279

1280
    /**
1281
     * Deletes the file properties metadata.
1282
     */
1283
    public void deleteMetadata() {
1284
        this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE);
1✔
1285
    }
1✔
1286

1287
    /**
1288
     * Deletes the file metadata of specified template type.
1289
     *
1290
     * @param typeName the metadata template type name.
1291
     */
1292
    public void deleteMetadata(String typeName) {
1293
        String scope = Metadata.scopeBasedOnType(typeName);
1✔
1294
        this.deleteMetadata(typeName, scope);
1✔
1295
    }
1✔
1296

1297
    /**
1298
     * Deletes the file metadata of specified template type.
1299
     *
1300
     * @param typeName the metadata template type name.
1301
     * @param scope    the metadata scope (global or enterprise).
1302
     */
1303
    public void deleteMetadata(String typeName, String scope) {
1304
        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1✔
1305
        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
1✔
1306
        request.send().close();
1✔
1307
    }
1✔
1308

1309
    /**
1310
     * Used to retrieve the watermark for the file.
1311
     * If the file does not have a watermark applied to it, a 404 Not Found will be returned by API.
1312
     *
1313
     * @param fields the fields to retrieve.
1314
     * @return the watermark associated with the file.
1315
     */
1316
    public BoxWatermark getWatermark(String... fields) {
1317
        return this.getWatermark(FILE_URL_TEMPLATE, fields);
1✔
1318
    }
1319

1320
    /**
1321
     * Used to apply or update the watermark for the file.
1322
     *
1323
     * @return the watermark associated with the file.
1324
     */
1325
    public BoxWatermark applyWatermark() {
1326
        return this.applyWatermark(FILE_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT);
1✔
1327
    }
1328

1329
    /**
1330
     * Removes a watermark from the file.
1331
     * If the file did not have a watermark applied to it, a 404 Not Found will be returned by API.
1332
     */
1333
    public void removeWatermark() {
1334
        this.removeWatermark(FILE_URL_TEMPLATE);
1✔
1335
    }
1✔
1336

1337
    /**
1338
     * {@inheritDoc}
1339
     */
1340
    @Override
1341
    public BoxFile.Info setCollections(BoxCollection... collections) {
1342
        JsonArray jsonArray = new JsonArray();
×
1343
        for (BoxCollection collection : collections) {
×
1344
            JsonObject collectionJSON = new JsonObject();
×
1345
            collectionJSON.add("id", collection.getID());
×
1346
            jsonArray.add(collectionJSON);
×
1347
        }
1348
        JsonObject infoJSON = new JsonObject();
×
1349
        infoJSON.add("collections", jsonArray);
×
1350

1351
        String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString();
×
1352
        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
×
1353
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
×
1354
        request.setBody(infoJSON.toString());
×
1355
        try (BoxJSONResponse response = request.send()) {
×
1356
            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
×
1357
            return new Info(jsonObject);
×
1358
        }
1359
    }
1360

1361
    /**
1362
     * Creates an upload session to create a new version of a file in chunks.
1363
     * This will first verify that the version can be created and then open a session for uploading pieces of the file.
1364
     *
1365
     * @param fileSize the size of the file that will be uploaded.
1366
     * @return the created upload session instance.
1367
     */
1368
    public BoxFileUploadSession.Info createUploadSession(long fileSize) {
1369
        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
×
1370

1371
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
×
1372
        request.addHeader("Content-Type", APPLICATION_JSON);
×
1373

1374
        JsonObject body = new JsonObject();
×
1375
        body.add("file_size", fileSize);
×
1376
        request.setBody(body.toString());
×
1377

1378
        try (BoxJSONResponse response = request.send()) {
×
1379
            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
×
1380

1381
            String sessionId = jsonObject.get("id").asString();
×
1382
            BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId);
×
1383
            return session.new Info(jsonObject);
×
1384
        }
1385
    }
1386

1387
    /**
1388
     * Creates a new version of a file.
1389
     *
1390
     * @param inputStream the stream instance that contains the data.
1391
     * @param fileSize    the size of the file that will be uploaded.
1392
     * @return the created file instance.
1393
     * @throws InterruptedException when a thread execution is interrupted.
1394
     * @throws IOException          when reading a stream throws exception.
1395
     */
1396
    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize)
1397
        throws InterruptedException, IOException {
1398
        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
×
1399
        return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize);
×
1400
    }
1401

1402
    /**
1403
     * Creates a new version of a file.  Also sets file attributes.
1404
     *
1405
     * @param inputStream    the stream instance that contains the data.
1406
     * @param fileSize       the size of the file that will be uploaded.
1407
     * @param fileAttributes file attributes to set
1408
     * @return the created file instance.
1409
     * @throws InterruptedException when a thread execution is interrupted.
1410
     * @throws IOException          when reading a stream throws exception.
1411
     */
1412
    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize, Map<String, String> fileAttributes)
1413
        throws InterruptedException, IOException {
1414
        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1✔
1415
        return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize, fileAttributes);
1✔
1416
    }
1417

1418
    /**
1419
     * Creates a new version of a file using specified number of parallel http connections.
1420
     *
1421
     * @param inputStream          the stream instance that contains the data.
1422
     * @param fileSize             the size of the file that will be uploaded.
1423
     * @param nParallelConnections number of parallel http connections to use
1424
     * @param timeOut              time to wait before killing the job
1425
     * @param unit                 time unit for the time wait value
1426
     * @return the created file instance.
1427
     * @throws InterruptedException when a thread execution is interrupted.
1428
     * @throws IOException          when reading a stream throws exception.
1429
     */
1430
    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize,
1431
                                        int nParallelConnections, long timeOut, TimeUnit unit)
1432
        throws InterruptedException, IOException {
1433
        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
×
1434
        return new LargeFileUpload(nParallelConnections, timeOut, unit)
×
1435
            .upload(this.getAPI(), inputStream, url, fileSize);
×
1436
    }
1437

1438
    /**
1439
     * Creates a new version of a file using specified number of parallel http connections.  Also sets file attributes.
1440
     *
1441
     * @param inputStream          the stream instance that contains the data.
1442
     * @param fileSize             the size of the file that will be uploaded.
1443
     * @param nParallelConnections number of parallel http connections to use
1444
     * @param timeOut              time to wait before killing the job
1445
     * @param unit                 time unit for the time wait value
1446
     * @param fileAttributes       file attributes to set
1447
     * @return the created file instance.
1448
     * @throws InterruptedException when a thread execution is interrupted.
1449
     * @throws IOException          when reading a stream throws exception.
1450
     */
1451
    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize,
1452
                                        int nParallelConnections, long timeOut, TimeUnit unit,
1453
                                        Map<String, String> fileAttributes)
1454
        throws InterruptedException, IOException {
1455
        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
×
1456
        return new LargeFileUpload(nParallelConnections, timeOut, unit)
×
1457
            .upload(this.getAPI(), inputStream, url, fileSize, fileAttributes);
×
1458
    }
1459

1460
    private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role,
1461
                                              Boolean notify, Boolean canViewPath, Date expiresAt,
1462
                                              Boolean isAccessOnly) {
1463

1464
        JsonObject itemField = new JsonObject();
1✔
1465
        itemField.add("id", this.getID());
1✔
1466
        itemField.add("type", "file");
1✔
1467

1468
        return BoxCollaboration.create(this.getAPI(), accessibleByField, itemField, role, notify, canViewPath,
1✔
1469
            expiresAt, isAccessOnly);
1470
    }
1471

1472
    /**
1473
     * Adds a collaborator to this file.
1474
     *
1475
     * @param collaborator the collaborator to add.
1476
     * @param role         the role of the collaborator.
1477
     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1478
     * @param canViewPath  whether view path collaboration feature is enabled or not.
1479
     * @param expiresAt    when the collaboration should expire.
1480
     * @param isAccessOnly whether the collaboration is access only or not.
1481
     * @return info about the new collaboration.
1482
     */
1483
    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role,
1484
                                             Boolean notify, Boolean canViewPath,
1485
                                             Date expiresAt, Boolean isAccessOnly) {
1486
        JsonObject accessibleByField = new JsonObject();
1✔
1487
        accessibleByField.add("id", collaborator.getID());
1✔
1488

1489
        if (collaborator instanceof BoxUser) {
1✔
1490
            accessibleByField.add("type", "user");
1✔
1491
        } else if (collaborator instanceof BoxGroup) {
×
1492
            accessibleByField.add("type", "group");
×
1493
        } else {
1494
            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
×
1495
        }
1496
        return this.collaborate(accessibleByField, role, notify, canViewPath, expiresAt, isAccessOnly);
1✔
1497
    }
1498

1499
    /**
1500
     * Adds a collaborator to this file.
1501
     *
1502
     * @param collaborator the collaborator to add.
1503
     * @param role         the role of the collaborator.
1504
     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1505
     * @param canViewPath  whether view path collaboration feature is enabled or not.
1506
     * @return info about the new collaboration.
1507
     */
1508
    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role,
1509
                                             Boolean notify, Boolean canViewPath) {
1510
        return this.collaborate(collaborator, role, notify, canViewPath, null, null);
×
1511
    }
1512

1513
    /**
1514
     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
1515
     * account.
1516
     *
1517
     * @param email        the email address of the collaborator to add.
1518
     * @param role         the role of the collaborator.
1519
     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1520
     * @param canViewPath  whether view path collaboration feature is enabled or not.
1521
     * @param expiresAt    when the collaboration should expire.
1522
     * @param isAccessOnly whether the collaboration is access only or not.
1523
     * @return info about the new collaboration.
1524
     */
1525
    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role,
1526
                                             Boolean notify, Boolean canViewPath,
1527
                                             Date expiresAt, Boolean isAccessOnly) {
1528
        JsonObject accessibleByField = new JsonObject();
1✔
1529
        accessibleByField.add("login", email);
1✔
1530
        accessibleByField.add("type", "user");
1✔
1531

1532
        return this.collaborate(accessibleByField, role, notify, canViewPath, expiresAt, isAccessOnly);
1✔
1533
    }
1534

1535
    /**
1536
     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
1537
     * account.
1538
     *
1539
     * @param email       the email address of the collaborator to add.
1540
     * @param role        the role of the collaborator.
1541
     * @param notify      determines if the user (or all the users in the group) will receive email notifications.
1542
     * @param canViewPath whether view path collaboration feature is enabled or not.
1543
     * @return info about the new collaboration.
1544
     */
1545
    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role,
1546
                                             Boolean notify, Boolean canViewPath) {
1547
        return this.collaborate(email, role, notify, canViewPath, null, null);
×
1548
    }
1549

1550
    /**
1551
     * Used to retrieve all collaborations associated with the item.
1552
     *
1553
     * @param fields the optional fields to retrieve.
1554
     * @return An iterable of metadata instances associated with the item.
1555
     */
1556
    public BoxResourceIterable<BoxCollaboration.Info> getAllFileCollaborations(String... fields) {
1557
        return BoxCollaboration.getAllFileCollaborations(this.getAPI(), this.getID(),
×
1558
            GET_COLLABORATORS_PAGE_SIZE, fields);
1559

1560
    }
1561

1562
    /**
1563
     * Used to specify what filetype to request for a file thumbnail.
1564
     */
1565
    public enum ThumbnailFileType {
×
1566
        /**
1567
         * PNG image format.
1568
         */
1569
        PNG,
×
1570

1571
        /**
1572
         * JPG image format.
1573
         */
1574
        JPG
×
1575
    }
1576

1577
    /**
1578
     * Enumerates the possible permissions that a user can have on a file.
1579
     */
1580
    public enum Permission {
1✔
1581
        /**
1582
         * The user can download the file.
1583
         */
1584
        CAN_DOWNLOAD("can_download"),
1✔
1585

1586
        /**
1587
         * The user can upload new versions of the file.
1588
         */
1589
        CAN_UPLOAD("can_upload"),
1✔
1590

1591
        /**
1592
         * The user can rename the file.
1593
         */
1594
        CAN_RENAME("can_rename"),
1✔
1595

1596
        /**
1597
         * The user can delete the file.
1598
         */
1599
        CAN_DELETE("can_delete"),
1✔
1600

1601
        /**
1602
         * The user can share the file.
1603
         */
1604
        CAN_SHARE("can_share"),
1✔
1605

1606
        /**
1607
         * The user can set the access level for shared links to the file.
1608
         */
1609
        CAN_SET_SHARE_ACCESS("can_set_share_access"),
1✔
1610

1611
        /**
1612
         * The user can preview the file.
1613
         */
1614
        CAN_PREVIEW("can_preview"),
1✔
1615

1616
        /**
1617
         * The user can comment on the file.
1618
         */
1619
        CAN_COMMENT("can_comment"),
1✔
1620

1621
        /**
1622
         * The user can place annotations on this file.
1623
         */
1624
        CAN_ANNOTATE("can_annotate"),
1✔
1625

1626
        /**
1627
         * The current user can invite new users to collaborate on this item, and the user can update the role of a
1628
         * user already collaborated on this item.
1629
         */
1630
        CAN_INVITE_COLLABORATOR("can_invite_collaborator"),
1✔
1631

1632
        /**
1633
         * The user can view all annotations placed on this file.
1634
         */
1635
        CAN_VIEW_ANNOTATIONS_ALL("can_view_annotations_all"),
1✔
1636

1637
        /**
1638
         * The user can view annotations placed by themselves on this file.
1639
         */
1640
        CAN_VIEW_ANNOTATIONS_SELF("can_view_annotations_self");
1✔
1641

1642
        private final String jsonValue;
1643

1644
        Permission(String jsonValue) {
1✔
1645
            this.jsonValue = jsonValue;
1✔
1646
        }
1✔
1647

1648
        static Permission fromJSONValue(String jsonValue) {
1649
            return Permission.valueOf(jsonValue.toUpperCase());
1✔
1650
        }
1651

1652
        String toJSONValue() {
1653
            return this.jsonValue;
×
1654
        }
1655
    }
1656

1657
    /**
1658
     * Contains information about a BoxFile.
1659
     */
1660
    public class Info extends BoxItem.Info {
1661
        private String sha1;
1662
        private String versionNumber;
1663
        private long commentCount;
1664
        private EnumSet<Permission> permissions;
1665
        private String extension;
1666
        private boolean isPackage;
1667
        private BoxFileVersion version;
1668
        private URL previewLink;
1669
        private BoxLock lock;
1670
        private boolean isWatermarked;
1671
        private boolean isExternallyOwned;
1672
        private Map<String, Map<String, Metadata>> metadataMap;
1673
        private List<Representation> representations;
1674
        private List<String> allowedInviteeRoles;
1675
        private Boolean hasCollaborations;
1676
        private String uploaderDisplayName;
1677
        private BoxClassification classification;
1678
        private Date dispositionAt;
1679
        private boolean isAccessibleViaSharedLink;
1680

1681
        /**
1682
         * Constructs an empty Info object.
1683
         */
1684
        public Info() {
1✔
1685
            super();
1✔
1686
        }
1✔
1687

1688
        /**
1689
         * Constructs an Info object by parsing information from a JSON string.
1690
         *
1691
         * @param json the JSON string to parse.
1692
         */
1693
        public Info(String json) {
1✔
1694
            super(json);
1✔
1695
        }
1✔
1696

1697
        /**
1698
         * Constructs an Info object using an already parsed JSON object.
1699
         *
1700
         * @param jsonObject the parsed JSON object.
1701
         */
1702
        public Info(JsonObject jsonObject) {
1✔
1703
            super(jsonObject);
1✔
1704
        }
1✔
1705

1706
        @Override
1707
        public BoxFile getResource() {
1708
            return BoxFile.this;
1✔
1709
        }
1710

1711
        /**
1712
         * Gets the SHA1 hash of the file.
1713
         *
1714
         * @return the SHA1 hash of the file.
1715
         */
1716
        public String getSha1() {
1717
            return this.sha1;
×
1718
        }
1719

1720
        /**
1721
         * Gets the lock of the file.
1722
         *
1723
         * @return the lock of the file.
1724
         */
1725
        public BoxLock getLock() {
1726
            return this.lock;
1✔
1727
        }
1728

1729
        /**
1730
         * Gets the current version number of the file.
1731
         *
1732
         * @return the current version number of the file.
1733
         */
1734
        public String getVersionNumber() {
1735
            return this.versionNumber;
×
1736
        }
1737

1738
        /**
1739
         * Gets the number of comments on the file.
1740
         *
1741
         * @return the number of comments on the file.
1742
         */
1743
        public long getCommentCount() {
1744
            return this.commentCount;
×
1745
        }
1746

1747
        /**
1748
         * Gets the permissions that the current user has on the file.
1749
         *
1750
         * @return the permissions that the current user has on the file.
1751
         */
1752
        public EnumSet<Permission> getPermissions() {
1753
            return this.permissions;
1✔
1754
        }
1755

1756
        /**
1757
         * Gets the extension suffix of the file, excluding the dot.
1758
         *
1759
         * @return the extension of the file.
1760
         */
1761
        public String getExtension() {
1762
            return this.extension;
×
1763
        }
1764

1765
        /**
1766
         * Gets whether or not the file is an OSX package.
1767
         *
1768
         * @return true if the file is an OSX package; otherwise false.
1769
         */
1770
        public boolean getIsPackage() {
1771
            return this.isPackage;
×
1772
        }
1773

1774
        /**
1775
         * Gets the current version details of the file.
1776
         *
1777
         * @return the current version details of the file.
1778
         */
1779
        public BoxFileVersion getVersion() {
1780
            return this.version;
1✔
1781
        }
1782

1783
        /**
1784
         * Gets the current expiring preview link.
1785
         *
1786
         * @return the expiring preview link
1787
         */
1788
        public URL getPreviewLink() {
1789
            return this.previewLink;
×
1790
        }
1791

1792
        /**
1793
         * Gets flag indicating whether this file is Watermarked.
1794
         *
1795
         * @return whether the file is watermarked or not
1796
         */
1797
        public boolean getIsWatermarked() {
1798
            return this.isWatermarked;
×
1799
        }
1800

1801
        /**
1802
         * Returns the allowed invitee roles for this file item.
1803
         *
1804
         * @return the list of roles allowed for invited collaborators.
1805
         */
1806
        public List<String> getAllowedInviteeRoles() {
1807
            return this.allowedInviteeRoles;
1✔
1808
        }
1809

1810
        /**
1811
         * Returns the indicator for whether this file item has collaborations.
1812
         *
1813
         * @return indicator for whether this file item has collaborations.
1814
         */
1815
        public Boolean getHasCollaborations() {
1816
            return this.hasCollaborations;
1✔
1817
        }
1818

1819
        /**
1820
         * Gets the metadata on this file associated with a specified scope and template.
1821
         * Makes an attempt to get metadata that was retrieved using getInfo(String ...) method.
1822
         *
1823
         * @param templateName the metadata template type name.
1824
         * @param scope        the scope of the template (usually "global" or "enterprise").
1825
         * @return the metadata returned from the server.
1826
         */
1827
        public Metadata getMetadata(String templateName, String scope) {
1828
            try {
1829
                return this.metadataMap.get(scope).get(templateName);
1✔
1830
            } catch (NullPointerException e) {
×
1831
                return null;
×
1832
            }
1833
        }
1834

1835
        /**
1836
         * Returns the field for indicating whether a file is owned by a user outside the enterprise.
1837
         *
1838
         * @return indicator for whether or not the file is owned by a user outside the enterprise.
1839
         */
1840
        public boolean getIsExternallyOwned() {
1841
            return this.isExternallyOwned;
1✔
1842
        }
1843

1844
        /**
1845
         * Get file's representations.
1846
         *
1847
         * @return list of representations
1848
         */
1849
        public List<Representation> getRepresentations() {
1850
            return this.representations;
1✔
1851
        }
1852

1853
        /**
1854
         * Returns user's name at the time of upload.
1855
         *
1856
         * @return user's name at the time of upload
1857
         */
1858
        public String getUploaderDisplayName() {
1859
            return this.uploaderDisplayName;
1✔
1860
        }
1861

1862
        /**
1863
         * Gets the metadata classification type of this file.
1864
         *
1865
         * @return the metadata classification type of this file.
1866
         */
1867
        public BoxClassification getClassification() {
1868
            return this.classification;
1✔
1869
        }
1870

1871
        /**
1872
         * Returns the retention expiration timestamp for the given file.
1873
         *
1874
         * @return Date representing expiration timestamp
1875
         */
1876
        public Date getDispositionAt() {
1877
            return dispositionAt;
1✔
1878
        }
1879

1880
        /**
1881
         * Modifies the retention expiration timestamp for the given file.
1882
         * This date cannot be shortened once set on a file.
1883
         *
1884
         * @param dispositionAt Date representing expiration timestamp
1885
         */
1886
        public void setDispositionAt(Date dispositionAt) {
1887
            this.dispositionAt = dispositionAt;
1✔
1888
            this.addPendingChange("disposition_at", BoxDateFormat.format(dispositionAt));
1✔
1889
        }
1✔
1890

1891
        /**
1892
         * Returns the flag indicating whether the file is accessible via a shared link.
1893
         *
1894
         * @return boolean flag indicating whether the file is accessible via a shared link.
1895
         */
1896
        public boolean getIsAccessibleViaSharedLink() {
1897
            return this.isAccessibleViaSharedLink;
1✔
1898
        }
1899

1900
        @Override
1901
        protected void parseJSONMember(JsonObject.Member member) {
1902
            super.parseJSONMember(member);
1✔
1903

1904
            String memberName = member.getName();
1✔
1905
            JsonValue value = member.getValue();
1✔
1906
            try {
1907
                switch (memberName) {
1✔
1908
                    case "sha1":
1909
                        this.sha1 = value.asString();
1✔
1910
                        break;
1✔
1911
                    case "version_number":
1912
                        this.versionNumber = value.asString();
1✔
1913
                        break;
1✔
1914
                    case "comment_count":
1915
                        this.commentCount = value.asLong();
1✔
1916
                        break;
1✔
1917
                    case "permissions":
1918
                        this.permissions = this.parsePermissions(value.asObject());
1✔
1919
                        break;
1✔
1920
                    case "extension":
1921
                        this.extension = value.asString();
1✔
1922
                        break;
1✔
1923
                    case "is_package":
1924
                        this.isPackage = value.asBoolean();
1✔
1925
                        break;
1✔
1926
                    case "has_collaborations":
1927
                        this.hasCollaborations = value.asBoolean();
1✔
1928
                        break;
1✔
1929
                    case "is_externally_owned":
1930
                        this.isExternallyOwned = value.asBoolean();
1✔
1931
                        break;
1✔
1932
                    case "file_version":
1933
                        this.version = this.parseFileVersion(value.asObject());
1✔
1934
                        break;
1✔
1935
                    case "allowed_invitee_roles":
1936
                        this.allowedInviteeRoles = this.parseAllowedInviteeRoles(value.asArray());
1✔
1937
                        break;
1✔
1938
                    case "expiring_embed_link":
1939
                        try {
1940
                            String urlString = member.getValue().asObject().get("url").asString();
1✔
1941
                            this.previewLink = new URL(urlString);
1✔
1942
                        } catch (MalformedURLException e) {
×
1943
                            throw new BoxAPIException("Couldn't parse expiring_embed_link/url for file", e);
×
1944
                        }
1✔
1945
                        break;
1946
                    case "lock":
1947
                        if (value.isNull()) {
1✔
1948
                            this.lock = null;
×
1949
                        } else {
1950
                            this.lock = new BoxLock(value.asObject(), BoxFile.this.getAPI());
1✔
1951
                        }
1952
                        break;
1✔
1953
                    case "watermark_info":
1954
                        this.isWatermarked = value.asObject().get("is_watermarked").asBoolean();
1✔
1955
                        break;
1✔
1956
                    case "metadata":
1957
                        this.metadataMap = Parsers.parseAndPopulateMetadataMap(value.asObject());
1✔
1958
                        break;
1✔
1959
                    case "representations":
1960
                        this.representations = Parsers.parseRepresentations(value.asObject());
1✔
1961
                        break;
1✔
1962
                    case "uploader_display_name":
1963
                        this.uploaderDisplayName = value.asString();
1✔
1964
                        break;
1✔
1965
                    case "classification":
1966
                        if (value.isNull()) {
1✔
1967
                            this.classification = null;
×
1968
                        } else {
1969
                            this.classification = new BoxClassification(value.asObject());
1✔
1970
                        }
1971
                        break;
1✔
1972
                    case "disposition_at":
1973
                        this.dispositionAt = BoxDateFormat.parse(value.asString());
1✔
1974
                        break;
1✔
1975
                    case "is_accessible_via_shared_link":
1976
                        this.isAccessibleViaSharedLink = value.asBoolean();
1✔
1977
                        break;
1✔
1978
                    default:
1979
                        break;
1980
                }
1981
            } catch (Exception e) {
×
1982
                throw new BoxDeserializationException(memberName, value.toString(), e);
×
1983
            }
1✔
1984
        }
1✔
1985

1986
        @SuppressWarnings("checkstyle:MissingSwitchDefault")
1987
        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
1988
            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
1✔
1989
            for (JsonObject.Member member : jsonObject) {
1✔
1990
                JsonValue value = member.getValue();
1✔
1991
                if (value.isNull() || !value.asBoolean()) {
1✔
1992
                    continue;
×
1993
                }
1994
                try {
1995
                    permissions.add(Permission.fromJSONValue(member.getName()));
1✔
1996
                } catch (IllegalArgumentException ignored) {
×
1997
                    // If the permission is not recognized, we ignore it.
1998
                }
1✔
1999
            }
1✔
2000

2001
            return permissions;
1✔
2002
        }
2003

2004
        private BoxFileVersion parseFileVersion(JsonObject jsonObject) {
2005
            return new BoxFileVersion(BoxFile.this.getAPI(), jsonObject, BoxFile.this.getID());
1✔
2006
        }
2007

2008
        private List<String> parseAllowedInviteeRoles(JsonArray jsonArray) {
2009
            List<String> roles = new ArrayList<>(jsonArray.size());
1✔
2010
            for (JsonValue value : jsonArray) {
1✔
2011
                roles.add(value.asString());
1✔
2012
            }
1✔
2013

2014
            return roles;
1✔
2015
        }
2016
    }
2017

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

© 2026 Coveralls, Inc