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