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