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