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