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