001package com.box.sdk;
002
003import java.net.URL;
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Date;
007
008import com.eclipsesource.json.JsonArray;
009import com.eclipsesource.json.JsonObject;
010import com.eclipsesource.json.JsonValue;
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     * @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
070
071        String queryString = "";
072        if (notify != null) {
073            queryString = new QueryStringBuilder().appendParam("notify", notify.toString()).toString();
074        }
075        URL url;
076        if (queryString.length() > 0) {
077            url = COLLABORATIONS_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), queryString);
078        } else {
079            url = COLLABORATIONS_URL_TEMPLATE.build(api.getBaseURL());
080        }
081
082        JsonObject requestJSON = new JsonObject();
083        requestJSON.add("item", item);
084        requestJSON.add("accessible_by", accessibleBy);
085        requestJSON.add("role", role.toJSONString());
086        if (canViewPath != null) {
087            requestJSON.add("can_view_path", canViewPath.booleanValue());
088        }
089
090        BoxJSONRequest request = new BoxJSONRequest(api, url, "POST");
091
092        request.setBody(requestJSON.toString());
093        BoxJSONResponse response = (BoxJSONResponse) request.send();
094        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
095
096        BoxCollaboration newCollaboration = new BoxCollaboration(api, responseJSON.get("id").asString());
097        return newCollaboration.new Info(responseJSON);
098    }
099
100    /**
101     * Gets all pending collaboration invites for the current user.
102     *
103     * @param api the API connection to use.
104     * @return a collection of pending collaboration infos.
105     */
106    public static Collection<Info> getPendingCollaborations(BoxAPIConnection api) {
107        URL url = PENDING_COLLABORATIONS_URL.build(api.getBaseURL());
108
109        BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
110        BoxJSONResponse response = (BoxJSONResponse) request.send();
111        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
112
113        int entriesCount = responseJSON.get("total_count").asInt();
114        Collection<BoxCollaboration.Info> collaborations = new ArrayList<BoxCollaboration.Info>(entriesCount);
115        JsonArray entries = responseJSON.get("entries").asArray();
116        for (JsonValue entry : entries) {
117            JsonObject entryObject = entry.asObject();
118            BoxCollaboration collaboration = new BoxCollaboration(api, entryObject.get("id").asString());
119            BoxCollaboration.Info info = collaboration.new Info(entryObject);
120            collaborations.add(info);
121        }
122
123        return collaborations;
124    }
125
126    /**
127     * Gets information about this collaboration.
128     *
129     * @return info about this collaboration.
130     */
131    public Info getInfo() {
132        BoxAPIConnection api = this.getAPI();
133        URL url = COLLABORATION_URL_TEMPLATE.build(api.getBaseURL(), this.getID());
134
135        BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
136        BoxJSONResponse response = (BoxJSONResponse) request.send();
137        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
138        return new Info(jsonObject);
139    }
140
141    /**
142     * Gets information about this collection with a custom set of fields.
143     * @param fields the fields to retrieve.
144     * @return info about the collaboration.
145     */
146    public Info getInfo(String... fields) {
147
148        String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
149        URL url = COLLABORATION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
150
151        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
152        BoxJSONResponse response = (BoxJSONResponse) request.send();
153        return new Info(response.getJSON());
154    }
155
156    /**
157     * Updates the information about this collaboration with any info fields that have been modified locally.
158     *
159     * @param info the updated info.
160     */
161    public void updateInfo(Info info) {
162        BoxAPIConnection api = this.getAPI();
163        URL url = COLLABORATION_URL_TEMPLATE.build(api.getBaseURL(), this.getID());
164
165        BoxJSONRequest request = new BoxJSONRequest(api, url, "PUT");
166        request.setBody(info.getPendingChanges());
167        BoxAPIResponse boxAPIResponse = request.send();
168
169        if (boxAPIResponse instanceof BoxJSONResponse) {
170            BoxJSONResponse response = (BoxJSONResponse) boxAPIResponse;
171            JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
172            info.update(jsonObject);
173        }
174    }
175
176    /**
177     * Deletes this collaboration.
178     */
179    public void delete() {
180        BoxAPIConnection api = this.getAPI();
181        URL url = COLLABORATION_URL_TEMPLATE.build(api.getBaseURL(), this.getID());
182
183        BoxAPIRequest request = new BoxAPIRequest(api, url, "DELETE");
184        BoxAPIResponse response = request.send();
185        response.disconnect();
186    }
187
188    /**
189     * Contains information about a BoxCollaboration.
190     */
191    public class Info extends BoxResource.Info {
192        private BoxUser.Info createdBy;
193        private Date createdAt;
194        private Date modifiedAt;
195        private Date expiresAt;
196        private Status status;
197        private BoxCollaborator.Info accessibleBy;
198        private Role role;
199        private Date acknowledgedAt;
200        private BoxFolder.Info item;
201        private BoxFile.Info fileItem;
202        private String inviteEmail;
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         * The email address used to invite an un-registered collaborator, if they are not a registered user.
274         * @return the email for the un-registed collaborator.
275         */
276        public String getInviteEmail() {
277            return this.inviteEmail;
278        }
279
280        /**
281         * Gets the status of the collaboration.
282         *
283         * @return the status of the collaboration.
284         */
285        public Status getStatus() {
286            return this.status;
287        }
288
289        /**
290         * Sets the status of the collaboration in order to accept or reject the collaboration if it's pending.
291         *
292         * @param status the new status of the collaboration.
293         */
294        public void setStatus(Status status) {
295            this.status = status;
296            this.addPendingChange("status", status.name().toLowerCase());
297        }
298
299        /**
300         * Gets the collaborator who this collaboration applies to.
301         *
302         * @return the collaborator who this collaboration applies to.
303         */
304        public BoxCollaborator.Info getAccessibleBy() {
305            return this.accessibleBy;
306        }
307
308        /**
309         * Gets the level of access the collaborator has.
310         *
311         * @return the level of access the collaborator has.
312         */
313        public Role getRole() {
314            return this.role;
315        }
316
317        /**
318         * Sets the level of access the collaborator has.
319         *
320         * @param role the new level of access to give the collaborator.
321         */
322        public void setRole(Role role) {
323            this.role = role;
324            this.addPendingChange("role", role.toJSONString());
325        }
326
327        /**
328         * Sets the permission for "view path collaboration" feature. When set to true this allows
329         * the invitee to to see the entire parent path to the item
330         *
331         * @param canViewState the boolean value indicating whether the invitee can see the parent folder.
332         */
333        public void setCanViewPath(boolean canViewState) {
334            this.canViewPath = canViewState;
335            this.addPendingChange("can_view_path", canViewState);
336        }
337
338        /**
339         * Gets the time the collaboration's status was changed.
340         *
341         * @return the time the collaboration's status was changed.
342         */
343        public Date getAcknowledgedAt() {
344            return this.acknowledgedAt;
345        }
346
347        /**
348         * Gets the folder the collaboration is related to.
349         *
350         * @return the folder the collaboration is related to.
351         */
352        public BoxFolder.Info getItem() {
353            return this.item;
354        }
355
356        @Override
357        public BoxCollaboration getResource() {
358            return BoxCollaboration.this;
359        }
360
361        @Override
362        protected void parseJSONMember(JsonObject.Member member) {
363            super.parseJSONMember(member);
364
365            String memberName = member.getName();
366            JsonValue value = member.getValue();
367            try {
368                if (memberName.equals("created_by")) {
369                    JsonObject userJSON = value.asObject();
370                    if (this.createdBy == null) {
371                        String userID = userJSON.get("id").asString();
372                        BoxUser user = new BoxUser(getAPI(), userID);
373                        this.createdBy = user.new Info(userJSON);
374                    } else {
375                        this.createdBy.update(userJSON);
376                    }
377
378                } else if (memberName.equals("created_at")) {
379                    this.createdAt = BoxDateFormat.parse(value.asString());
380
381                } else if (memberName.equals("modified_at")) {
382                    this.modifiedAt = BoxDateFormat.parse(value.asString());
383
384                } else if (memberName.equals("expires_at")) {
385                    this.expiresAt = BoxDateFormat.parse(value.asString());
386
387                } else if (memberName.equals("status")) {
388                    String statusString = value.asString().toUpperCase();
389                    this.status = Status.valueOf(statusString);
390
391                } else if (memberName.equals("accessible_by")) {
392                    JsonObject accessibleByJSON = value.asObject();
393                    if (this.accessibleBy == null) {
394                        this.accessibleBy = this.parseAccessibleBy(accessibleByJSON);
395                    } else {
396                        this.updateAccessibleBy(accessibleByJSON);
397                    }
398                } else if (memberName.equals("role")) {
399                    this.role = Role.fromJSONString(value.asString());
400
401                } else if (memberName.equals("acknowledged_at")) {
402                    this.acknowledgedAt = BoxDateFormat.parse(value.asString());
403
404                } else if (memberName.equals("can_view_path")) {
405                    this.canViewPath = value.asBoolean();
406
407                } else if (memberName.equals("invite_email")) {
408                    this.inviteEmail = value.asString();
409
410                } else if (memberName.equals("item")) {
411                    JsonObject folderJSON = value.asObject();
412                    if (this.item == null) {
413                        String folderID = folderJSON.get("id").asString();
414                        BoxFolder folder = new BoxFolder(getAPI(), folderID);
415                        this.item = folder.new Info(folderJSON);
416                    } else {
417                        this.item.update(folderJSON);
418                    }
419                }
420            } catch (Exception e) {
421                throw new BoxDeserializationException(memberName, value.toString(), e);
422            }
423        }
424
425        private void updateAccessibleBy(JsonObject json) {
426            String type = json.get("type").asString();
427            if ((type.equals("user") && this.accessibleBy instanceof BoxUser.Info)
428                    || (type.equals("group") && this.accessibleBy instanceof BoxGroup.Info)) {
429
430                this.accessibleBy.update(json);
431            } else {
432                this.accessibleBy = this.parseAccessibleBy(json);
433            }
434        }
435
436        private BoxCollaborator.Info parseAccessibleBy(JsonObject json) {
437            String id = json.get("id").asString();
438            String type = json.get("type").asString();
439            BoxCollaborator.Info parsedInfo = null;
440            if (type.equals("user")) {
441                BoxUser user = new BoxUser(getAPI(), id);
442                parsedInfo = user.new Info(json);
443            } else if (type.equals("group")) {
444                BoxGroup group = new BoxGroup(getAPI(), id);
445                parsedInfo = group.new Info(json);
446            }
447
448            return parsedInfo;
449        }
450    }
451
452    /**
453     * Enumerates the possible statuses that a collaboration can have.
454     */
455    public enum Status {
456        /**
457         * The collaboration has been accepted.
458         */
459        ACCEPTED,
460
461        /**
462         * The collaboration is waiting to be accepted or rejected.
463         */
464        PENDING,
465
466        /**
467         * The collaboration has been rejected.
468         */
469        REJECTED;
470    }
471
472    /**
473     * Enumerates the possible access levels that a collaborator can have.
474     */
475    public enum Role {
476        /**
477         * An Editor has full read/write access to a folder. Once invited to a folder, they will be able to view,
478         * download, upload, edit, delete, copy, move, rename, generate shared links, make comments, assign tasks,
479         * create tags, and invite/remove collaborators. They will not be able to delete or move root level folders.
480         */
481        EDITOR("editor"),
482
483        /**
484         * The viewer role has full read access to a folder. Once invited to a folder, they will be able to preview,
485         * download, make comments, and generate shared links.  They will not be able to add tags, invite new
486         * collaborators, upload, edit, or delete items in the folder.
487         */
488        VIEWER("viewer"),
489
490        /**
491         * The previewer role has limited read access to a folder. They will only be able to preview the items in the
492         * folder using the integrated content viewer. They will not be able to share, upload, edit, or delete any
493         * content. This role is only available to enterprise accounts.
494         */
495        PREVIEWER("previewer"),
496
497        /**
498         * The uploader has limited write access to a folder. They will only be able to upload and see the names of the
499         * items in a folder. They will not able to download or view any content. This role is only available to
500         * enterprise accounts.
501         */
502        UPLOADER("uploader"),
503
504        /**
505         * The previewer-uploader role is a combination of previewer and uploader. A user with this access level will be
506         * able to preview files using the integrated content viewer as well as upload items into the folder. They will
507         * not be able to download, edit, or share, items in the folder. This role is only available to enterprise
508         * accounts.
509         */
510        PREVIEWER_UPLOADER("previewer uploader"),
511
512        /**
513         * The viewer-uploader role is a combination of viewer and uploader. A viewer-uploader has full read access to a
514         * folder and limited write access. They are able to preview, download, add comments, generate shared links, and
515         * upload content to the folder. They will not be able to add tags, invite new collaborators, edit, or delete
516         * items in the folder. This role is only available to enterprise accounts.
517         */
518        VIEWER_UPLOADER("viewer uploader"),
519
520        /**
521         * The co-owner role has all of the functional read/write access that an editor does. This permission level has
522         * the added ability of being able to manage users in the folder. A co-owner can add new collaborators, change
523         * access levels of existing collaborators, and remove collaborators. However, they will not be able to
524         * manipulate the owner of the folder or transfer ownership to another user. This role is only available to
525         * enterprise accounts.
526         */
527        CO_OWNER("co-owner"),
528
529        /**
530         * The owner role has all of the functional capabilities of a co-owner. However, they will be able to manipulate
531         * the owner of the folder or transfer ownership to another user. This role is only available to enterprise
532         * accounts.
533         */
534        OWNER("owner");
535
536        private final String jsonValue;
537
538        private Role(String jsonValue) {
539            this.jsonValue = jsonValue;
540        }
541
542        static Role fromJSONString(String jsonValue) {
543            if (jsonValue.equals("editor")) {
544                return EDITOR;
545            } else if (jsonValue.equals("viewer")) {
546                return VIEWER;
547            } else if (jsonValue.equals("previewer")) {
548                return PREVIEWER;
549            } else if (jsonValue.equals("uploader")) {
550                return UPLOADER;
551            } else if (jsonValue.equals("previewer uploader")) {
552                return PREVIEWER_UPLOADER;
553            } else if (jsonValue.equals("viewer uploader")) {
554                return VIEWER_UPLOADER;
555            } else if (jsonValue.equals("co-owner")) {
556                return CO_OWNER;
557            } else if (jsonValue.equals("owner")) {
558                return OWNER;
559            } else {
560                throw new IllegalArgumentException("The provided JSON value isn't a valid Role.");
561            }
562        }
563
564        String toJSONString() {
565            return this.jsonValue;
566        }
567    }
568
569    /**
570     * Used to retrieve all collaborations associated with the item.
571     *
572     * @param api   BoxAPIConnection from the associated file.
573     * @param fileID   FileID of the associated file
574     * @param pageSize   page size for server pages of the Iterable
575     * @param fields the optional fields to retrieve.
576     * @return An iterable of BoxCollaboration.Info instances associated with the item.
577     */
578    public static BoxResourceIterable<Info> getAllFileCollaborations(final BoxAPIConnection api, String fileID,
579                                                                           int pageSize, String... fields) {
580        QueryStringBuilder builder = new QueryStringBuilder();
581        if (fields.length > 0) {
582            builder.appendParam("fields", fields);
583        }
584        return new BoxResourceIterable<BoxCollaboration.Info>(
585                api, GET_ALL_FILE_COLLABORATIONS_URL.buildWithQuery(api.getBaseURL(), builder.toString(), fileID),
586                pageSize) {
587
588            @Override
589            protected BoxCollaboration.Info factory(JsonObject jsonObject) {
590                String id = jsonObject.get("id").asString();
591                return new BoxCollaboration(api, id).new Info(jsonObject);
592            }
593        };
594    }
595}