001package com.box.sdk;
002
003import com.eclipsesource.json.Json;
004import com.eclipsesource.json.JsonArray;
005import com.eclipsesource.json.JsonObject;
006import com.eclipsesource.json.JsonValue;
007import java.net.URL;
008import java.util.ArrayList;
009import java.util.Collection;
010import java.util.Date;
011
012/**
013 * Represents a collaboration between a user and another user or group. Collaborations are Box's equivalent of access
014 * control lists. They can be used to set and apply permissions for users or groups to folders.
015 *
016 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked
017 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error
018 * handling for errors related to the Box REST API, you should capture this exception explicitly.</p>
019 */
020@BoxResourceType("collaboration")
021public class BoxCollaboration extends BoxResource {
022
023    /**
024     * All possible fields on a collaboration object.
025     */
026    public static final String[] ALL_FIELDS = {"type", "id", "item", "accessible_by", "role", "expires_at",
027        "can_view_path", "status", "acknowledged_at", "created_by",
028        "created_at", "modified_at"};
029
030    /**
031     * Collaborations URL Template.
032     */
033    public static final URLTemplate COLLABORATIONS_URL_TEMPLATE = new URLTemplate("collaborations");
034    /**
035     * Pending Collaborations URL.
036     */
037    public static final URLTemplate PENDING_COLLABORATIONS_URL = new URLTemplate("collaborations?status=pending");
038    /**
039     * Collaboration URL Template.
040     */
041    public static final URLTemplate COLLABORATION_URL_TEMPLATE = new URLTemplate("collaborations/%s");
042    /**
043     * Get All File Collaboations URL.
044     */
045    public static final URLTemplate GET_ALL_FILE_COLLABORATIONS_URL = new URLTemplate("files/%s/collaborations");
046
047    /**
048     * Constructs a BoxCollaboration for a collaboration with a given ID.
049     *
050     * @param api the API connection to be used by the collaboration.
051     * @param id  the ID of the collaboration.
052     */
053    public BoxCollaboration(BoxAPIConnection api, String id) {
054        super(api, id);
055    }
056
057    /**
058     * Create a new collaboration object.
059     *
060     * @param api          the API connection used to make the request.
061     * @param accessibleBy the JSON object describing who should be collaborated.
062     * @param item         the JSON object describing which item to collaborate.
063     * @param role         the role to give the collaborators.
064     * @param notify       the user/group should receive email notification of the collaboration or not.
065     * @param canViewPath  the view path collaboration feature is enabled or not.
066     * @return info about the new collaboration.
067     */
068    protected static BoxCollaboration.Info create(BoxAPIConnection api, JsonObject accessibleBy, JsonObject item,
069                                                  BoxCollaboration.Role role, Boolean notify, Boolean canViewPath) {
070        return create(api, accessibleBy, item, role, notify, canViewPath, null);
071    }
072
073    /**
074     * Create a new collaboration object.
075     *
076     * @param api          the API connection used to make the request.
077     * @param accessibleBy the JSON object describing who should be collaborated.
078     * @param item         the JSON object describing which item to collaborate.
079     * @param role         the role to give the collaborators.
080     * @param notify       the user/group should receive email notification of the collaboration or not.
081     * @param canViewPath  the view path collaboration feature is enabled or not.
082     * @param expiresAt    the date the collaboration expires
083     * @return info about the new collaboration.
084     */
085    protected static BoxCollaboration.Info create(
086        BoxAPIConnection api,
087        JsonObject accessibleBy,
088        JsonObject item,
089        BoxCollaboration.Role role,
090        Boolean notify,
091        Boolean canViewPath,
092        Date expiresAt
093    ) {
094
095        String queryString = "";
096        if (notify != null) {
097            queryString = new QueryStringBuilder().appendParam("notify", notify.toString()).toString();
098        }
099        URL url;
100        if (queryString.length() > 0) {
101            url = COLLABORATIONS_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), queryString);
102        } else {
103            url = COLLABORATIONS_URL_TEMPLATE.build(api.getBaseURL());
104        }
105
106        JsonObject requestJSON = new JsonObject();
107        requestJSON.add("item", item);
108        requestJSON.add("accessible_by", accessibleBy);
109        requestJSON.add("role", role.toJSONString());
110        if (canViewPath != null) {
111            requestJSON.add("can_view_path", canViewPath);
112        }
113        if (expiresAt != null) {
114            requestJSON.add("expires_at", BoxDateFormat.format(expiresAt));
115        }
116
117        BoxJSONRequest request = new BoxJSONRequest(api, url, "POST");
118
119        request.setBody(requestJSON.toString());
120        BoxJSONResponse response = (BoxJSONResponse) request.send();
121        JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
122
123        BoxCollaboration newCollaboration = new BoxCollaboration(api, responseJSON.get("id").asString());
124        return newCollaboration.new Info(responseJSON);
125    }
126
127    /**
128     * Gets all pending collaboration invites for the current user.
129     *
130     * @param api the API connection to use.
131     * @return a collection of pending collaboration infos.
132     */
133    public static Collection<Info> getPendingCollaborations(BoxAPIConnection api) {
134        URL url = PENDING_COLLABORATIONS_URL.build(api.getBaseURL());
135
136        BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
137        BoxJSONResponse response = (BoxJSONResponse) request.send();
138        JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
139
140        int entriesCount = responseJSON.get("total_count").asInt();
141        Collection<BoxCollaboration.Info> collaborations = new ArrayList<>(entriesCount);
142        JsonArray entries = responseJSON.get("entries").asArray();
143        for (JsonValue entry : entries) {
144            JsonObject entryObject = entry.asObject();
145            BoxCollaboration collaboration = new BoxCollaboration(api, entryObject.get("id").asString());
146            BoxCollaboration.Info info = collaboration.new Info(entryObject);
147            collaborations.add(info);
148        }
149
150        return collaborations;
151    }
152
153    /**
154     * Used to retrieve all collaborations associated with the item.
155     *
156     * @param api      BoxAPIConnection from the associated file.
157     * @param fileID   FileID of the associated file
158     * @param pageSize page size for server pages of the Iterable
159     * @param fields   the optional fields to retrieve.
160     * @return An iterable of BoxCollaboration.Info instances associated with the item.
161     */
162    public static BoxResourceIterable<Info> getAllFileCollaborations(final BoxAPIConnection api, String fileID,
163                                                                     int pageSize, String... fields) {
164        QueryStringBuilder builder = new QueryStringBuilder();
165        if (fields.length > 0) {
166            builder.appendParam("fields", fields);
167        }
168        return new BoxResourceIterable<BoxCollaboration.Info>(
169            api, GET_ALL_FILE_COLLABORATIONS_URL.buildWithQuery(api.getBaseURL(), builder.toString(), fileID),
170            pageSize) {
171
172            @Override
173            protected BoxCollaboration.Info factory(JsonObject jsonObject) {
174                String id = jsonObject.get("id").asString();
175                return new BoxCollaboration(api, id).new Info(jsonObject);
176            }
177        };
178    }
179
180    /**
181     * Gets information about this collaboration.
182     *
183     * @return info about this collaboration.
184     */
185    public Info getInfo() {
186        BoxAPIConnection api = this.getAPI();
187        URL url = COLLABORATION_URL_TEMPLATE.build(api.getBaseURL(), this.getID());
188
189        BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
190        BoxJSONResponse response = (BoxJSONResponse) request.send();
191        JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
192        return new Info(jsonObject);
193    }
194
195    /**
196     * Gets information about this collection with a custom set of fields.
197     *
198     * @param fields the fields to retrieve.
199     * @return info about the collaboration.
200     */
201    public Info getInfo(String... fields) {
202
203        String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
204        URL url = COLLABORATION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
205
206        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
207        BoxJSONResponse response = (BoxJSONResponse) request.send();
208        return new Info(response.getJSON());
209    }
210
211    /**
212     * Updates the information about this collaboration with any info fields that have been modified locally.
213     *
214     * @param info the updated info.
215     */
216    public void updateInfo(Info info) {
217        BoxAPIConnection api = this.getAPI();
218        URL url = COLLABORATION_URL_TEMPLATE.build(api.getBaseURL(), this.getID());
219
220        BoxJSONRequest request = new BoxJSONRequest(api, url, "PUT");
221        request.setBody(info.getPendingChanges());
222        BoxAPIResponse boxAPIResponse = request.send();
223
224        if (boxAPIResponse instanceof BoxJSONResponse) {
225            BoxJSONResponse response = (BoxJSONResponse) boxAPIResponse;
226            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
227            info.update(jsonObject);
228        }
229    }
230
231    /**
232     * Deletes this collaboration.
233     */
234    public void delete() {
235        BoxAPIConnection api = this.getAPI();
236        URL url = COLLABORATION_URL_TEMPLATE.build(api.getBaseURL(), this.getID());
237
238        BoxAPIRequest request = new BoxAPIRequest(api, url, "DELETE");
239        BoxAPIResponse response = request.send();
240        response.disconnect();
241    }
242
243    /**
244     * Enumerates the possible statuses that a collaboration can have.
245     */
246    public enum Status {
247        /**
248         * The collaboration has been accepted.
249         */
250        ACCEPTED,
251
252        /**
253         * The collaboration is waiting to be accepted or rejected.
254         */
255        PENDING,
256
257        /**
258         * The collaboration has been rejected.
259         */
260        REJECTED
261    }
262
263    /**
264     * Enumerates the possible access levels that a collaborator can have.
265     */
266    public enum Role {
267        /**
268         * An Editor has full read/write access to a folder. Once invited to a folder, they will be able to view,
269         * download, upload, edit, delete, copy, move, rename, generate shared links, make comments, assign tasks,
270         * create tags, and invite/remove collaborators. They will not be able to delete or move root level folders.
271         */
272        EDITOR("editor"),
273
274        /**
275         * The viewer role has full read access to a folder. Once invited to a folder, they will be able to preview,
276         * download, make comments, and generate shared links.  They will not be able to add tags, invite new
277         * collaborators, upload, edit, or delete items in the folder.
278         */
279        VIEWER("viewer"),
280
281        /**
282         * The previewer role has limited read access to a folder. They will only be able to preview the items in the
283         * folder using the integrated content viewer. They will not be able to share, upload, edit, or delete any
284         * content. This role is only available to enterprise accounts.
285         */
286        PREVIEWER("previewer"),
287
288        /**
289         * The uploader has limited write access to a folder. They will only be able to upload and see the names of the
290         * items in a folder. They will not able to download or view any content. This role is only available to
291         * enterprise accounts.
292         */
293        UPLOADER("uploader"),
294
295        /**
296         * The previewer-uploader role is a combination of previewer and uploader. A user with this access level will be
297         * able to preview files using the integrated content viewer as well as upload items into the folder. They will
298         * not be able to download, edit, or share, items in the folder. This role is only available to enterprise
299         * accounts.
300         */
301        PREVIEWER_UPLOADER("previewer uploader"),
302
303        /**
304         * The viewer-uploader role is a combination of viewer and uploader. A viewer-uploader has full read access to a
305         * folder and limited write access. They are able to preview, download, add comments, generate shared links, and
306         * upload content to the folder. They will not be able to add tags, invite new collaborators, edit, or delete
307         * items in the folder. This role is only available to enterprise accounts.
308         */
309        VIEWER_UPLOADER("viewer uploader"),
310
311        /**
312         * The co-owner role has all of the functional read/write access that an editor does. This permission level has
313         * the added ability of being able to manage users in the folder. A co-owner can add new collaborators, change
314         * access levels of existing collaborators, and remove collaborators. However, they will not be able to
315         * manipulate the owner of the folder or transfer ownership to another user. This role is only available to
316         * enterprise accounts.
317         */
318        CO_OWNER("co-owner"),
319
320        /**
321         * The owner role has all of the functional capabilities of a co-owner. However, they will be able to manipulate
322         * the owner of the folder or transfer ownership to another user. This role is only available to enterprise
323         * accounts.
324         */
325        OWNER("owner");
326
327        private final String jsonValue;
328
329        Role(String jsonValue) {
330            this.jsonValue = jsonValue;
331        }
332
333        static Role fromJSONString(String jsonValue) {
334            switch (jsonValue) {
335                case "editor":
336                    return EDITOR;
337                case "viewer":
338                    return VIEWER;
339                case "previewer":
340                    return PREVIEWER;
341                case "uploader":
342                    return UPLOADER;
343                case "previewer uploader":
344                    return PREVIEWER_UPLOADER;
345                case "viewer uploader":
346                    return VIEWER_UPLOADER;
347                case "co-owner":
348                    return CO_OWNER;
349                case "owner":
350                    return OWNER;
351                default:
352                    throw new IllegalArgumentException("The provided JSON value isn't a valid Role.");
353            }
354        }
355
356        String toJSONString() {
357            return this.jsonValue;
358        }
359    }
360
361    /**
362     * Contains information about a BoxCollaboration.
363     */
364    public class  Info extends BoxResource.Info {
365        private BoxUser.Info createdBy;
366        private Date createdAt;
367        private Date modifiedAt;
368        private Date expiresAt;
369        private Status status;
370        private BoxCollaborator.Info accessibleBy;
371        private Role role;
372        private Date acknowledgedAt;
373        private BoxItem.Info item;
374        private String inviteEmail;
375        private boolean canViewPath;
376
377        /**
378         * Constructs an empty Info object.
379         */
380        public Info() {
381            super();
382        }
383
384        /**
385         * Constructs an Info object by parsing information from a JSON string.
386         *
387         * @param json the JSON string to parse.
388         */
389        public Info(String json) {
390            super(json);
391        }
392
393        Info(JsonObject jsonObject) {
394            super(jsonObject);
395        }
396
397        /**
398         * Gets the user who created the collaboration.
399         *
400         * @return the user who created the collaboration.
401         */
402        public BoxUser.Info getCreatedBy() {
403            return this.createdBy;
404        }
405
406        /**
407         * Gets the time the collaboration was created.
408         *
409         * @return the time the collaboration was created.
410         */
411        public Date getCreatedAt() {
412            return this.createdAt;
413        }
414
415        /**
416         * Gets the time the collaboration was last modified.
417         *
418         * @return the time the collaboration was last modified.
419         */
420        public Date getModifiedAt() {
421            return this.modifiedAt;
422        }
423
424        /**
425         * Gets the time the collaboration will expire.
426         *
427         * @return the time the collaboration will expire.
428         */
429        public Date getExpiresAt() {
430            return this.expiresAt;
431        }
432
433        /**
434         * Set the time the collaboration will expire.
435         *
436         * @param expiresAt the expiration date of the collaboration.
437         */
438        public void setExpiresAt(Date expiresAt) {
439            this.expiresAt = expiresAt;
440            this.addPendingChange("expires_at", BoxDateFormat.format(expiresAt));
441        }
442
443        /**
444         * Gets a boolean indicator whether "view path collaboration" feature is enabled or not.
445         * When set to true this allows the invitee to see the entire parent path to the item.
446         * It is important to note that this does not grant privileges in any parent folder.
447         *
448         * @return the Boolean value indicating if "view path collaboration" is enabled or not
449         */
450        public boolean getCanViewPath() {
451            return this.canViewPath;
452        }
453
454        /**
455         * Sets the permission for "view path collaboration" feature. When set to true this allows
456         * the invitee to to see the entire parent path to the item
457         *
458         * @param canViewState the boolean value indicating whether the invitee can see the parent folder.
459         */
460        public void setCanViewPath(boolean canViewState) {
461            this.canViewPath = canViewState;
462            this.addPendingChange("can_view_path", canViewState);
463        }
464
465        /**
466         * The email address used to invite an un-registered collaborator, if they are not a registered user.
467         *
468         * @return the email for the un-registed collaborator.
469         */
470        public String getInviteEmail() {
471            return this.inviteEmail;
472        }
473
474        /**
475         * Gets the status of the collaboration.
476         *
477         * @return the status of the collaboration.
478         */
479        public Status getStatus() {
480            return this.status;
481        }
482
483        /**
484         * Sets the status of the collaboration in order to accept or reject the collaboration if it's pending.
485         *
486         * @param status the new status of the collaboration.
487         */
488        public void setStatus(Status status) {
489            this.status = status;
490            this.addPendingChange("status", status.name().toLowerCase());
491        }
492
493        /**
494         * Gets the collaborator who this collaboration applies to.
495         *
496         * @return the collaborator who this collaboration applies to.
497         */
498        public BoxCollaborator.Info getAccessibleBy() {
499            return this.accessibleBy;
500        }
501
502        /**
503         * Gets the level of access the collaborator has.
504         *
505         * @return the level of access the collaborator has.
506         */
507        public Role getRole() {
508            return this.role;
509        }
510
511        /**
512         * Sets the level of access the collaborator has.
513         *
514         * @param role the new level of access to give the collaborator.
515         */
516        public void setRole(Role role) {
517            this.role = role;
518            this.addPendingChange("role", role.toJSONString());
519        }
520
521        /**
522         * Gets the time the collaboration's status was changed.
523         *
524         * @return the time the collaboration's status was changed.
525         */
526        public Date getAcknowledgedAt() {
527            return this.acknowledgedAt;
528        }
529
530        /**
531         * Gets the folder the collaboration is related to.
532         *
533         * @return the folder the collaboration is related to.
534         */
535        public BoxItem.Info getItem() {
536            return this.item;
537        }
538
539        @Override
540        public BoxCollaboration getResource() {
541            return BoxCollaboration.this;
542        }
543
544        @SuppressWarnings("checkstyle:MissingSwitchDefault")
545        @Override
546        protected void parseJSONMember(JsonObject.Member member) {
547            super.parseJSONMember(member);
548
549            String memberName = member.getName();
550            JsonValue value = member.getValue();
551            try {
552                switch (memberName) {
553                    case "created_by":
554                        JsonObject userJSON = value.asObject();
555                        if (this.createdBy == null) {
556                            String userID = userJSON.get("id").asString();
557                            BoxUser user = new BoxUser(getAPI(), userID);
558                            this.createdBy = user.new Info(userJSON);
559                        } else {
560                            this.createdBy.update(userJSON);
561                        }
562                        break;
563                    case "created_at":
564                        this.createdAt = BoxDateFormat.parse(value.asString());
565
566                        break;
567                    case "modified_at":
568                        this.modifiedAt = BoxDateFormat.parse(value.asString());
569                        break;
570                    case "expires_at":
571                        this.expiresAt = BoxDateFormat.parse(value.asString());
572                        break;
573                    case "status":
574                        String statusString = value.asString().toUpperCase();
575                        this.status = Status.valueOf(statusString);
576
577                        break;
578                    case "accessible_by":
579                        JsonObject accessibleByJSON = value.asObject();
580                        if (this.accessibleBy == null) {
581                            this.accessibleBy = this.parseAccessibleBy(accessibleByJSON);
582                        } else {
583                            this.updateAccessibleBy(accessibleByJSON);
584                        }
585                        break;
586                    case "role":
587                        this.role = Role.fromJSONString(value.asString());
588                        break;
589                    case "acknowledged_at":
590                        this.acknowledgedAt = BoxDateFormat.parse(value.asString());
591                        break;
592                    case "can_view_path":
593                        this.canViewPath = value.asBoolean();
594                        break;
595                    case "invite_email":
596                        this.inviteEmail = value.asString();
597                        break;
598                    case "item":
599                        JsonObject itemJson = value.asObject();
600                        if (this.item == null) {
601                            this.item = selectCollaborationItem(itemJson);
602                        } else {
603                            this.item.update(itemJson);
604                        }
605                        break;
606                }
607            } catch (Exception e) {
608                throw new BoxDeserializationException(memberName, value.toString(), e);
609            }
610        }
611
612        private BoxItem.Info selectCollaborationItem(JsonObject itemJson) {
613            String itemId = itemJson.get("id").asString();
614            String itemType = itemJson.get("type").asString();
615            switch (itemType) {
616                case BoxFile.TYPE:
617                    return new BoxFile(getAPI(), itemId).new Info(itemJson);
618                case BoxFolder.TYPE:
619                    return new BoxFolder(getAPI(), itemId).new Info(itemJson);
620                default:
621                    throw new IllegalStateException(
622                        String.format(
623                            "Unsupported collaboration item type '%s': JSON %n%s",
624                            itemType,
625                            itemJson
626                        ));
627            }
628        }
629
630        private void updateAccessibleBy(JsonObject json) {
631            String type = json.get("type").asString();
632            if ((type.equals("user") && this.accessibleBy instanceof BoxUser.Info)
633                || (type.equals("group") && this.accessibleBy instanceof BoxGroup.Info)) {
634
635                this.accessibleBy.update(json);
636            } else {
637                this.accessibleBy = this.parseAccessibleBy(json);
638            }
639        }
640
641        private BoxCollaborator.Info parseAccessibleBy(JsonObject json) {
642            String id = json.get("id").asString();
643            String type = json.get("type").asString();
644            BoxCollaborator.Info parsedInfo = null;
645            if (type.equals("user")) {
646                BoxUser user = new BoxUser(getAPI(), id);
647                parsedInfo = user.new Info(json);
648            } else if (type.equals("group")) {
649                BoxGroup group = new BoxGroup(getAPI(), id);
650                parsedInfo = group.new Info(json);
651            }
652
653            return parsedInfo;
654        }
655    }
656}