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