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