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