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}