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