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