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 String inviteEmail;
204        private boolean canViewPath;
205
206        /**
207         * Constructs an empty Info object.
208         */
209        public Info() {
210            super();
211        }
212
213        /**
214         * Constructs an Info object by parsing information from a JSON string.
215         *
216         * @param json the JSON string to parse.
217         */
218        public Info(String json) {
219            super(json);
220        }
221
222        Info(JsonObject jsonObject) {
223            super(jsonObject);
224        }
225
226        /**
227         * Gets the user who created the collaboration.
228         *
229         * @return the user who created the collaboration.
230         */
231        public BoxUser.Info getCreatedBy() {
232            return this.createdBy;
233        }
234
235        /**
236         * Gets the time the collaboration was created.
237         *
238         * @return the time the collaboration was created.
239         */
240        public Date getCreatedAt() {
241            return this.createdAt;
242        }
243
244        /**
245         * Gets the time the collaboration was last modified.
246         *
247         * @return the time the collaboration was last modified.
248         */
249        public Date getModifiedAt() {
250            return this.modifiedAt;
251        }
252
253        /**
254         * Gets the time the collaboration will expire.
255         *
256         * @return the time the collaboration will expire.
257         */
258        public Date getExpiresAt() {
259            return this.expiresAt;
260        }
261
262        /**
263         * Gets a boolean indicator whether "view path collaboration" feature is enabled or not.
264         * When set to true this allows the invitee to see the entire parent path to the item.
265         * It is important to note that this does not grant privileges in any parent folder.
266         *
267         * @return the Boolean value indicating if "view path collaboration" is enabled or not
268         */
269        public boolean getCanViewPath() {
270            return this.canViewPath;
271        }
272
273        /**
274         * The email address used to invite an un-registered collaborator, if they are not a registered user.
275         * @return the email for the un-registed collaborator.
276         */
277        public String getInviteEmail() {
278            return this.inviteEmail;
279        }
280
281        /**
282         * Gets the status of the collaboration.
283         *
284         * @return the status of the collaboration.
285         */
286        public Status getStatus() {
287            return this.status;
288        }
289
290        /**
291         * Sets the status of the collaboration in order to accept or reject the collaboration if it's pending.
292         *
293         * @param status the new status of the collaboration.
294         */
295        public void setStatus(Status status) {
296            this.status = status;
297            this.addPendingChange("status", status.name().toLowerCase());
298        }
299
300        /**
301         * Gets the collaborator who this collaboration applies to.
302         *
303         * @return the collaborator who this collaboration applies to.
304         */
305        public BoxCollaborator.Info getAccessibleBy() {
306            return this.accessibleBy;
307        }
308
309        /**
310         * Gets the level of access the collaborator has.
311         *
312         * @return the level of access the collaborator has.
313         */
314        public Role getRole() {
315            return this.role;
316        }
317
318        /**
319         * Sets the level of access the collaborator has.
320         *
321         * @param role the new level of access to give the collaborator.
322         */
323        public void setRole(Role role) {
324            this.role = role;
325            this.addPendingChange("role", role.toJSONString());
326        }
327
328        /**
329         * Sets the permission for "view path collaboration" feature. When set to true this allows
330         * the invitee to to see the entire parent path to the item
331         *
332         * @param canViewState the boolean value indicating whether the invitee can see the parent folder.
333         */
334        public void setCanViewPath(boolean canViewState) {
335            this.canViewPath = canViewState;
336            this.addPendingChange("can_view_path", canViewState);
337        }
338
339        /**
340         * Gets the time the collaboration's status was changed.
341         *
342         * @return the time the collaboration's status was changed.
343         */
344        public Date getAcknowledgedAt() {
345            return this.acknowledgedAt;
346        }
347
348        /**
349         * Gets the folder the collaboration is related to.
350         *
351         * @return the folder the collaboration is related to.
352         */
353        public BoxFolder.Info getItem() {
354            return this.item;
355        }
356
357        @Override
358        public BoxCollaboration getResource() {
359            return BoxCollaboration.this;
360        }
361
362        @Override
363        protected void parseJSONMember(JsonObject.Member member) {
364            super.parseJSONMember(member);
365
366            String memberName = member.getName();
367            JsonValue value = member.getValue();
368            try {
369                if (memberName.equals("created_by")) {
370                    JsonObject userJSON = value.asObject();
371                    if (this.createdBy == null) {
372                        String userID = userJSON.get("id").asString();
373                        BoxUser user = new BoxUser(getAPI(), userID);
374                        this.createdBy = user.new Info(userJSON);
375                    } else {
376                        this.createdBy.update(userJSON);
377                    }
378
379                } else if (memberName.equals("created_at")) {
380                    this.createdAt = BoxDateFormat.parse(value.asString());
381
382                } else if (memberName.equals("modified_at")) {
383                    this.modifiedAt = BoxDateFormat.parse(value.asString());
384
385                } else if (memberName.equals("expires_at")) {
386                    this.expiresAt = BoxDateFormat.parse(value.asString());
387
388                } else if (memberName.equals("status")) {
389                    String statusString = value.asString().toUpperCase();
390                    this.status = Status.valueOf(statusString);
391
392                } else if (memberName.equals("accessible_by")) {
393                    JsonObject accessibleByJSON = value.asObject();
394                    if (this.accessibleBy == null) {
395                        this.accessibleBy = this.parseAccessibleBy(accessibleByJSON);
396                    } else {
397                        this.updateAccessibleBy(accessibleByJSON);
398                    }
399                } else if (memberName.equals("role")) {
400                    this.role = Role.fromJSONString(value.asString());
401
402                } else if (memberName.equals("acknowledged_at")) {
403                    this.acknowledgedAt = BoxDateFormat.parse(value.asString());
404
405                } else if (memberName.equals("can_view_path")) {
406                    this.canViewPath = value.asBoolean();
407
408                } else if (memberName.equals("invite_email")) {
409                    this.inviteEmail = value.asString();
410
411                } else if (memberName.equals("item")) {
412                    JsonObject folderJSON = value.asObject();
413                    if (this.item == null) {
414                        String folderID = folderJSON.get("id").asString();
415                        BoxFolder folder = new BoxFolder(getAPI(), folderID);
416                        this.item = folder.new Info(folderJSON);
417                    } else {
418                        this.item.update(folderJSON);
419                    }
420                }
421            } catch (ParseException e) {
422                assert false : "A ParseException indicates a bug in the SDK.";
423            }
424        }
425
426        private void updateAccessibleBy(JsonObject json) {
427            String type = json.get("type").asString();
428            if ((type.equals("user") && this.accessibleBy instanceof BoxUser.Info)
429                    || (type.equals("group") && this.accessibleBy instanceof BoxGroup.Info)) {
430
431                this.accessibleBy.update(json);
432            } else {
433                this.accessibleBy = this.parseAccessibleBy(json);
434            }
435        }
436
437        private BoxCollaborator.Info parseAccessibleBy(JsonObject json) {
438            String id = json.get("id").asString();
439            String type = json.get("type").asString();
440            BoxCollaborator.Info parsedInfo = null;
441            if (type.equals("user")) {
442                BoxUser user = new BoxUser(getAPI(), id);
443                parsedInfo = user.new Info(json);
444            } else if (type.equals("group")) {
445                BoxGroup group = new BoxGroup(getAPI(), id);
446                parsedInfo = group.new Info(json);
447            }
448
449            return parsedInfo;
450        }
451    }
452
453    /**
454     * Enumerates the possible statuses that a collaboration can have.
455     */
456    public enum Status {
457        /**
458         * The collaboration has been accepted.
459         */
460        ACCEPTED,
461
462        /**
463         * The collaboration is waiting to be accepted or rejected.
464         */
465        PENDING,
466
467        /**
468         * The collaboration has been rejected.
469         */
470        REJECTED;
471    }
472
473    /**
474     * Enumerates the possible access levels that a collaborator can have.
475     */
476    public enum Role {
477        /**
478         * An Editor has full read/write access to a folder. Once invited to a folder, they will be able to view,
479         * download, upload, edit, delete, copy, move, rename, generate shared links, make comments, assign tasks,
480         * create tags, and invite/remove collaborators. They will not be able to delete or move root level folders.
481         */
482        EDITOR("editor"),
483
484        /**
485         * The viewer role has full read access to a folder. Once invited to a folder, they will be able to preview,
486         * download, make comments, and generate shared links.  They will not be able to add tags, invite new
487         * collaborators, upload, edit, or delete items in the folder.
488         */
489        VIEWER("viewer"),
490
491        /**
492         * The previewer role has limited read access to a folder. They will only be able to preview the items in the
493         * folder using the integrated content viewer. They will not be able to share, upload, edit, or delete any
494         * content. This role is only available to enterprise accounts.
495         */
496        PREVIEWER("previewer"),
497
498        /**
499         * The uploader has limited write access to a folder. They will only be able to upload and see the names of the
500         * items in a folder. They will not able to download or view any content. This role is only available to
501         * enterprise accounts.
502         */
503        UPLOADER("uploader"),
504
505        /**
506         * The previewer-uploader role is a combination of previewer and uploader. A user with this access level will be
507         * able to preview files using the integrated content viewer as well as upload items into the folder. They will
508         * not be able to download, edit, or share, items in the folder. This role is only available to enterprise
509         * accounts.
510         */
511        PREVIEWER_UPLOADER("previewer uploader"),
512
513        /**
514         * The viewer-uploader role is a combination of viewer and uploader. A viewer-uploader has full read access to a
515         * folder and limited write access. They are able to preview, download, add comments, generate shared links, and
516         * upload content to the folder. They will not be able to add tags, invite new collaborators, edit, or delete
517         * items in the folder. This role is only available to enterprise accounts.
518         */
519        VIEWER_UPLOADER("viewer uploader"),
520
521        /**
522         * The co-owner role has all of the functional read/write access that an editor does. This permission level has
523         * the added ability of being able to manage users in the folder. A co-owner can add new collaborators, change
524         * access levels of existing collaborators, and remove collaborators. However, they will not be able to
525         * manipulate the owner of the folder or transfer ownership to another user. This role is only available to
526         * enterprise accounts.
527         */
528        CO_OWNER("co-owner"),
529
530        /**
531         * The owner role has all of the functional capabilities of a co-owner. However, they will be able to manipulate
532         * the owner of the folder or transfer ownership to another user. This role is only available to enterprise
533         * accounts.
534         */
535        OWNER("owner");
536
537        private final String jsonValue;
538
539        private Role(String jsonValue) {
540            this.jsonValue = jsonValue;
541        }
542
543        static Role fromJSONString(String jsonValue) {
544            if (jsonValue.equals("editor")) {
545                return EDITOR;
546            } else if (jsonValue.equals("viewer")) {
547                return VIEWER;
548            } else if (jsonValue.equals("previewer")) {
549                return PREVIEWER;
550            } else if (jsonValue.equals("uploader")) {
551                return UPLOADER;
552            } else if (jsonValue.equals("previewer uploader")) {
553                return PREVIEWER_UPLOADER;
554            } else if (jsonValue.equals("viewer uploader")) {
555                return VIEWER_UPLOADER;
556            } else if (jsonValue.equals("co-owner")) {
557                return CO_OWNER;
558            } else if (jsonValue.equals("owner")) {
559                return OWNER;
560            } else {
561                throw new IllegalArgumentException("The provided JSON value isn't a valid Role.");
562            }
563        }
564
565        String toJSONString() {
566            return this.jsonValue;
567        }
568    }
569
570    /**
571     * Used to retrieve all collaborations associated with the item.
572     *
573     * @param api   BoxAPIConnection from the associated file.
574     * @param fileID   FileID of the associated file
575     * @param pageSize   page size for server pages of the Iterable
576     * @param fields the optional fields to retrieve.
577     * @return An iterable of BoxCollaboration.Info instances associated with the item.
578     */
579    public static BoxResourceIterable<Info> getAllFileCollaborations(final BoxAPIConnection api, String fileID,
580                                                                           int pageSize, String... fields) {
581        QueryStringBuilder builder = new QueryStringBuilder();
582        if (fields.length > 0) {
583            builder.appendParam("fields", fields);
584        }
585        return new BoxResourceIterable<BoxCollaboration.Info>(
586                api, GET_ALL_FILE_COLLABORATIONS_URL.buildWithQuery(api.getBaseURL(), builder.toString(), fileID),
587                pageSize) {
588
589            @Override
590            protected BoxCollaboration.Info factory(JsonObject jsonObject) {
591                String id = jsonObject.get("id").asString();
592                return new BoxCollaboration(api, id).new Info(jsonObject);
593            }
594        };
595    }
596}