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