001package com.box.sdk; 002 003import com.eclipsesource.json.Json; 004import com.eclipsesource.json.JsonArray; 005import com.eclipsesource.json.JsonObject; 006import com.eclipsesource.json.JsonValue; 007import java.net.URL; 008import java.util.ArrayList; 009import java.util.Collection; 010import java.util.Date; 011 012/** 013 * Represents a collaboration between a user and another user or group. Collaborations are Box's equivalent of access 014 * control lists. They can be used to set and apply permissions for users or groups to folders. 015 * 016 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked 017 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error 018 * handling for errors related to the Box REST API, you should capture this exception explicitly.</p> 019 */ 020@BoxResourceType("collaboration") 021public class BoxCollaboration extends BoxResource { 022 023 /** 024 * All possible fields on a collaboration object. 025 */ 026 public static final String[] ALL_FIELDS = {"type", "id", "item", "accessible_by", "role", "expires_at", 027 "can_view_path", "status", "acknowledged_at", "created_by", 028 "created_at", "modified_at"}; 029 030 /** 031 * Collaborations URL Template. 032 */ 033 public static final URLTemplate COLLABORATIONS_URL_TEMPLATE = new URLTemplate("collaborations"); 034 /** 035 * Pending Collaborations URL. 036 */ 037 public static final URLTemplate PENDING_COLLABORATIONS_URL = new URLTemplate("collaborations?status=pending"); 038 /** 039 * Collaboration URL Template. 040 */ 041 public static final URLTemplate COLLABORATION_URL_TEMPLATE = new URLTemplate("collaborations/%s"); 042 /** 043 * Get All File Collaboations URL. 044 */ 045 public static final URLTemplate GET_ALL_FILE_COLLABORATIONS_URL = new URLTemplate("files/%s/collaborations"); 046 047 /** 048 * Constructs a BoxCollaboration for a collaboration with a given ID. 049 * 050 * @param api the API connection to be used by the collaboration. 051 * @param id the ID of the collaboration. 052 */ 053 public BoxCollaboration(BoxAPIConnection api, String id) { 054 super(api, id); 055 } 056 057 /** 058 * Create a new collaboration object. 059 * 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 return create(api, accessibleBy, item, role, notify, canViewPath, null); 071 } 072 073 /** 074 * Create a new collaboration object. 075 * 076 * @param api the API connection used to make the request. 077 * @param accessibleBy the JSON object describing who should be collaborated. 078 * @param item the JSON object describing which item to collaborate. 079 * @param role the role to give the collaborators. 080 * @param notify the user/group should receive email notification of the collaboration or not. 081 * @param canViewPath the view path collaboration feature is enabled or not. 082 * @param expiresAt the date the collaboration expires 083 * @return info about the new collaboration. 084 */ 085 protected static BoxCollaboration.Info create( 086 BoxAPIConnection api, 087 JsonObject accessibleBy, 088 JsonObject item, 089 BoxCollaboration.Role role, 090 Boolean notify, 091 Boolean canViewPath, 092 Date expiresAt 093 ) { 094 095 String queryString = ""; 096 if (notify != null) { 097 queryString = new QueryStringBuilder().appendParam("notify", notify.toString()).toString(); 098 } 099 URL url; 100 if (queryString.length() > 0) { 101 url = COLLABORATIONS_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), queryString); 102 } else { 103 url = COLLABORATIONS_URL_TEMPLATE.build(api.getBaseURL()); 104 } 105 106 JsonObject requestJSON = new JsonObject(); 107 requestJSON.add("item", item); 108 requestJSON.add("accessible_by", accessibleBy); 109 requestJSON.add("role", role.toJSONString()); 110 if (canViewPath != null) { 111 requestJSON.add("can_view_path", canViewPath); 112 } 113 if (expiresAt != null) { 114 requestJSON.add("expires_at", BoxDateFormat.format(expiresAt)); 115 } 116 117 BoxJSONRequest request = new BoxJSONRequest(api, url, "POST"); 118 119 request.setBody(requestJSON.toString()); 120 try (BoxJSONResponse response = request.send()) { 121 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 122 BoxCollaboration newCollaboration = new BoxCollaboration(api, responseJSON.get("id").asString()); 123 return newCollaboration.new Info(responseJSON); 124 } 125 } 126 127 /** 128 * Gets all pending collaboration invites for the current user. 129 * 130 * @param api the API connection to use. 131 * @return a collection of pending collaboration infos. 132 */ 133 public static Collection<Info> getPendingCollaborations(BoxAPIConnection api) { 134 URL url = PENDING_COLLABORATIONS_URL.build(api.getBaseURL()); 135 136 BoxJSONRequest request = new BoxJSONRequest(api, url, "GET"); 137 try (BoxJSONResponse response = request.send()) { 138 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 139 140 int entriesCount = responseJSON.get("total_count").asInt(); 141 Collection<BoxCollaboration.Info> collaborations = new ArrayList<>(entriesCount); 142 JsonArray entries = responseJSON.get("entries").asArray(); 143 for (JsonValue entry : entries) { 144 JsonObject entryObject = entry.asObject(); 145 BoxCollaboration collaboration = new BoxCollaboration(api, entryObject.get("id").asString()); 146 BoxCollaboration.Info info = collaboration.new Info(entryObject); 147 collaborations.add(info); 148 } 149 150 return collaborations; 151 } 152 } 153 154 /** 155 * Used to retrieve all collaborations associated with the item. 156 * 157 * @param api BoxAPIConnection from the associated file. 158 * @param fileID FileID of the associated file 159 * @param pageSize page size for server pages of the Iterable 160 * @param fields the optional fields to retrieve. 161 * @return An iterable of BoxCollaboration.Info instances associated with the item. 162 */ 163 public static BoxResourceIterable<Info> getAllFileCollaborations(final BoxAPIConnection api, String fileID, 164 int pageSize, String... fields) { 165 QueryStringBuilder builder = new QueryStringBuilder(); 166 if (fields.length > 0) { 167 builder.appendParam("fields", fields); 168 } 169 return new BoxResourceIterable<BoxCollaboration.Info>( 170 api, GET_ALL_FILE_COLLABORATIONS_URL.buildWithQuery(api.getBaseURL(), builder.toString(), fileID), 171 pageSize) { 172 173 @Override 174 protected BoxCollaboration.Info factory(JsonObject jsonObject) { 175 String id = jsonObject.get("id").asString(); 176 return new BoxCollaboration(api, id).new Info(jsonObject); 177 } 178 }; 179 } 180 181 /** 182 * Gets information about this collection with a custom set of fields. 183 * 184 * @param fields the fields to retrieve. 185 * @return info about the collaboration. 186 */ 187 public Info getInfo(String... fields) { 188 URL url = COLLABORATION_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID()); 189 if (fields.length > 0) { 190 String queryString = new QueryStringBuilder().appendParam("fields", fields).toString(); 191 url = COLLABORATION_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID()); 192 } 193 194 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); 195 try (BoxJSONResponse response = request.send()) { 196 return new Info(response.getJSON()); 197 } 198 } 199 200 /** 201 * Updates the information about this collaboration with any info fields that have been modified locally. 202 * 203 * @param info the updated info. 204 */ 205 public void updateInfo(Info info) { 206 BoxAPIConnection api = this.getAPI(); 207 URL url = COLLABORATION_URL_TEMPLATE.build(api.getBaseURL(), this.getID()); 208 209 BoxJSONRequest request = new BoxJSONRequest(api, url, "PUT"); 210 request.setBody(info.getPendingChanges()); 211 try (BoxJSONResponse response = request.send()) { 212 JsonObject jsonObject = Json.parse(response.getJSON()).asObject(); 213 info.update(jsonObject); 214 } 215 } 216 217 /** 218 * Deletes this collaboration. 219 */ 220 public void delete() { 221 BoxAPIConnection api = this.getAPI(); 222 URL url = COLLABORATION_URL_TEMPLATE.build(api.getBaseURL(), this.getID()); 223 224 BoxAPIRequest request = new BoxAPIRequest(api, url, "DELETE"); 225 request.send().close(); 226 } 227 228 /** 229 * Enumerates the possible statuses that a collaboration can have. 230 */ 231 public enum Status { 232 /** 233 * The collaboration has been accepted. 234 */ 235 ACCEPTED, 236 237 /** 238 * The collaboration is waiting to be accepted or rejected. 239 */ 240 PENDING, 241 242 /** 243 * The collaboration has been rejected. 244 */ 245 REJECTED 246 } 247 248 /** 249 * Enumerates the possible access levels that a collaborator can have. 250 */ 251 public enum Role { 252 /** 253 * An Editor has full read/write access to a folder. Once invited to a folder, they will be able to view, 254 * download, upload, edit, delete, copy, move, rename, generate shared links, make comments, assign tasks, 255 * create tags, and invite/remove collaborators. They will not be able to delete or move root level folders. 256 */ 257 EDITOR("editor"), 258 259 /** 260 * The viewer role has full read access to a folder. Once invited to a folder, they will be able to preview, 261 * download, make comments, and generate shared links. They will not be able to add tags, invite new 262 * collaborators, upload, edit, or delete items in the folder. 263 */ 264 VIEWER("viewer"), 265 266 /** 267 * The previewer role has limited read access to a folder. They will only be able to preview the items in the 268 * folder using the integrated content viewer. They will not be able to share, upload, edit, or delete any 269 * content. This role is only available to enterprise accounts. 270 */ 271 PREVIEWER("previewer"), 272 273 /** 274 * The uploader has limited write access to a folder. They will only be able to upload and see the names of the 275 * items in a folder. They will not able to download or view any content. This role is only available to 276 * enterprise accounts. 277 */ 278 UPLOADER("uploader"), 279 280 /** 281 * The previewer-uploader role is a combination of previewer and uploader. A user with this access level will be 282 * able to preview files using the integrated content viewer as well as upload items into the folder. They will 283 * not be able to download, edit, or share, items in the folder. This role is only available to enterprise 284 * accounts. 285 */ 286 PREVIEWER_UPLOADER("previewer uploader"), 287 288 /** 289 * The viewer-uploader role is a combination of viewer and uploader. A viewer-uploader has full read access to a 290 * folder and limited write access. They are able to preview, download, add comments, generate shared links, and 291 * upload content to the folder. They will not be able to add tags, invite new collaborators, edit, or delete 292 * items in the folder. This role is only available to enterprise accounts. 293 */ 294 VIEWER_UPLOADER("viewer uploader"), 295 296 /** 297 * The co-owner role has all of the functional read/write access that an editor does. This permission level has 298 * the added ability of being able to manage users in the folder. A co-owner can add new collaborators, change 299 * access levels of existing collaborators, and remove collaborators. However, they will not be able to 300 * manipulate the owner of the folder or transfer ownership to another user. This role is only available to 301 * enterprise accounts. 302 */ 303 CO_OWNER("co-owner"), 304 305 /** 306 * The owner role has all of the functional capabilities of a co-owner. However, they will be able to manipulate 307 * the owner of the folder or transfer ownership to another user. This role is only available to enterprise 308 * accounts. 309 */ 310 OWNER("owner"); 311 312 private final String jsonValue; 313 314 Role(String jsonValue) { 315 this.jsonValue = jsonValue; 316 } 317 318 static Role fromJSONString(String jsonValue) { 319 switch (jsonValue) { 320 case "editor": 321 return EDITOR; 322 case "viewer": 323 return VIEWER; 324 case "previewer": 325 return PREVIEWER; 326 case "uploader": 327 return UPLOADER; 328 case "previewer uploader": 329 return PREVIEWER_UPLOADER; 330 case "viewer uploader": 331 return VIEWER_UPLOADER; 332 case "co-owner": 333 return CO_OWNER; 334 case "owner": 335 return OWNER; 336 default: 337 throw new IllegalArgumentException("The provided JSON value isn't a valid Role."); 338 } 339 } 340 341 String toJSONString() { 342 return this.jsonValue; 343 } 344 } 345 346 /** 347 * Contains information about a BoxCollaboration. 348 */ 349 public class Info extends BoxResource.Info { 350 private BoxUser.Info createdBy; 351 private Date createdAt; 352 private Date modifiedAt; 353 private Date expiresAt; 354 private Status status; 355 private BoxCollaborator.Info accessibleBy; 356 private Role role; 357 private Date acknowledgedAt; 358 private BoxItem.Info item; 359 private String inviteEmail; 360 private boolean canViewPath; 361 362 /** 363 * Constructs an empty Info object. 364 */ 365 public Info() { 366 super(); 367 } 368 369 /** 370 * Constructs an Info object by parsing information from a JSON string. 371 * 372 * @param json the JSON string to parse. 373 */ 374 public Info(String json) { 375 super(json); 376 } 377 378 Info(JsonObject jsonObject) { 379 super(jsonObject); 380 } 381 382 /** 383 * Gets the user who created the collaboration. 384 * 385 * @return the user who created the collaboration. 386 */ 387 public BoxUser.Info getCreatedBy() { 388 return this.createdBy; 389 } 390 391 /** 392 * Gets the time the collaboration was created. 393 * 394 * @return the time the collaboration was created. 395 */ 396 public Date getCreatedAt() { 397 return this.createdAt; 398 } 399 400 /** 401 * Gets the time the collaboration was last modified. 402 * 403 * @return the time the collaboration was last modified. 404 */ 405 public Date getModifiedAt() { 406 return this.modifiedAt; 407 } 408 409 /** 410 * Gets the time the collaboration will expire. 411 * 412 * @return the time the collaboration will expire. 413 */ 414 public Date getExpiresAt() { 415 return this.expiresAt; 416 } 417 418 /** 419 * Set the time the collaboration will expire. 420 * 421 * @param expiresAt the expiration date of the collaboration. 422 */ 423 public void setExpiresAt(Date expiresAt) { 424 this.expiresAt = expiresAt; 425 this.addPendingChange("expires_at", BoxDateFormat.format(expiresAt)); 426 } 427 428 /** 429 * Gets a boolean indicator whether "view path collaboration" feature is enabled or not. 430 * When set to true this allows the invitee to see the entire parent path to the item. 431 * It is important to note that this does not grant privileges in any parent folder. 432 * 433 * @return the Boolean value indicating if "view path collaboration" is enabled or not 434 */ 435 public boolean getCanViewPath() { 436 return this.canViewPath; 437 } 438 439 /** 440 * Sets the permission for "view path collaboration" feature. When set to true this allows 441 * the invitee to to see the entire parent path to the item 442 * 443 * @param canViewState the boolean value indicating whether the invitee can see the parent folder. 444 */ 445 public void setCanViewPath(boolean canViewState) { 446 this.canViewPath = canViewState; 447 this.addPendingChange("can_view_path", canViewState); 448 } 449 450 /** 451 * The email address used to invite an un-registered collaborator, if they are not a registered user. 452 * 453 * @return the email for the un-registed collaborator. 454 */ 455 public String getInviteEmail() { 456 return this.inviteEmail; 457 } 458 459 /** 460 * Gets the status of the collaboration. 461 * 462 * @return the status of the collaboration. 463 */ 464 public Status getStatus() { 465 return this.status; 466 } 467 468 /** 469 * Sets the status of the collaboration in order to accept or reject the collaboration if it's pending. 470 * 471 * @param status the new status of the collaboration. 472 */ 473 public void setStatus(Status status) { 474 this.status = status; 475 this.addPendingChange("status", status.name().toLowerCase()); 476 } 477 478 /** 479 * Gets the collaborator who this collaboration applies to. 480 * 481 * @return the collaborator who this collaboration applies to. 482 */ 483 public BoxCollaborator.Info getAccessibleBy() { 484 return this.accessibleBy; 485 } 486 487 /** 488 * Gets the level of access the collaborator has. 489 * 490 * @return the level of access the collaborator has. 491 */ 492 public Role getRole() { 493 return this.role; 494 } 495 496 /** 497 * Sets the level of access the collaborator has. 498 * 499 * @param role the new level of access to give the collaborator. 500 */ 501 public void setRole(Role role) { 502 this.role = role; 503 this.addPendingChange("role", role.toJSONString()); 504 } 505 506 /** 507 * Gets the time the collaboration's status was changed. 508 * 509 * @return the time the collaboration's status was changed. 510 */ 511 public Date getAcknowledgedAt() { 512 return this.acknowledgedAt; 513 } 514 515 /** 516 * Gets the folder the collaboration is related to. 517 * 518 * @return the folder the collaboration is related to. 519 */ 520 public BoxItem.Info getItem() { 521 return this.item; 522 } 523 524 @Override 525 public BoxCollaboration getResource() { 526 return BoxCollaboration.this; 527 } 528 529 @SuppressWarnings("checkstyle:MissingSwitchDefault") 530 @Override 531 protected void parseJSONMember(JsonObject.Member member) { 532 super.parseJSONMember(member); 533 534 String memberName = member.getName(); 535 JsonValue value = member.getValue(); 536 try { 537 switch (memberName) { 538 case "created_by": 539 JsonObject userJSON = value.asObject(); 540 if (this.createdBy == null) { 541 String userID = userJSON.get("id").asString(); 542 BoxUser user = new BoxUser(getAPI(), userID); 543 this.createdBy = user.new Info(userJSON); 544 } else { 545 this.createdBy.update(userJSON); 546 } 547 break; 548 case "created_at": 549 this.createdAt = BoxDateFormat.parse(value.asString()); 550 551 break; 552 case "modified_at": 553 this.modifiedAt = BoxDateFormat.parse(value.asString()); 554 break; 555 case "expires_at": 556 this.expiresAt = BoxDateFormat.parse(value.asString()); 557 break; 558 case "status": 559 String statusString = value.asString().toUpperCase(); 560 this.status = Status.valueOf(statusString); 561 562 break; 563 case "accessible_by": 564 JsonObject accessibleByJSON = value.asObject(); 565 if (this.accessibleBy == null) { 566 this.accessibleBy = this.parseAccessibleBy(accessibleByJSON); 567 } else { 568 this.updateAccessibleBy(accessibleByJSON); 569 } 570 break; 571 case "role": 572 this.role = Role.fromJSONString(value.asString()); 573 break; 574 case "acknowledged_at": 575 this.acknowledgedAt = BoxDateFormat.parse(value.asString()); 576 break; 577 case "can_view_path": 578 this.canViewPath = value.asBoolean(); 579 break; 580 case "invite_email": 581 this.inviteEmail = value.asString(); 582 break; 583 case "item": 584 JsonObject itemJson = value.asObject(); 585 if (this.item == null) { 586 this.item = selectCollaborationItem(itemJson); 587 } else { 588 this.item.update(itemJson); 589 } 590 break; 591 } 592 } catch (Exception e) { 593 throw new BoxDeserializationException(memberName, value.toString(), e); 594 } 595 } 596 597 private BoxItem.Info selectCollaborationItem(JsonObject itemJson) { 598 String itemId = itemJson.get("id").asString(); 599 String itemType = itemJson.get("type").asString(); 600 switch (itemType) { 601 case BoxFile.TYPE: 602 return new BoxFile(getAPI(), itemId).new Info(itemJson); 603 case BoxFolder.TYPE: 604 return new BoxFolder(getAPI(), itemId).new Info(itemJson); 605 default: 606 throw new IllegalStateException( 607 String.format( 608 "Unsupported collaboration item type '%s': JSON %n%s", 609 itemType, 610 itemJson 611 )); 612 } 613 } 614 615 private void updateAccessibleBy(JsonObject json) { 616 String type = json.get("type").asString(); 617 if ((type.equals("user") && this.accessibleBy instanceof BoxUser.Info) 618 || (type.equals("group") && this.accessibleBy instanceof BoxGroup.Info)) { 619 620 this.accessibleBy.update(json); 621 } else { 622 this.accessibleBy = this.parseAccessibleBy(json); 623 } 624 } 625 626 private BoxCollaborator.Info parseAccessibleBy(JsonObject json) { 627 String id = json.get("id").asString(); 628 String type = json.get("type").asString(); 629 BoxCollaborator.Info parsedInfo = null; 630 if (type.equals("user")) { 631 BoxUser user = new BoxUser(getAPI(), id); 632 parsedInfo = user.new Info(json); 633 } else if (type.equals("group")) { 634 BoxGroup group = new BoxGroup(getAPI(), id); 635 parsedInfo = group.new Info(json); 636 } 637 638 return parsedInfo; 639 } 640 } 641}