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