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