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