001package com.box.sdk;
002
003import com.eclipsesource.json.Json;
004import com.eclipsesource.json.JsonArray;
005import com.eclipsesource.json.JsonObject;
006import com.eclipsesource.json.JsonValue;
007import java.net.URL;
008import java.util.ArrayList;
009import java.util.Date;
010import java.util.HashSet;
011import java.util.List;
012import java.util.Set;
013
014/** The abstract base class for items in a user's file tree (files, folders, etc.). */
015public abstract class BoxItem extends BoxResource {
016  /**
017   * An array of all possible file fields that can be requested when calling {@link
018   * #getInfo(String...)}.
019   */
020  public static final String[] ALL_FIELDS = {
021    "type",
022    "id",
023    "sequence_id",
024    "etag",
025    "sha1",
026    "name",
027    "description",
028    "size",
029    "path_collection",
030    "created_at",
031    "modified_at",
032    "trashed_at",
033    "purged_at",
034    "content_created_at",
035    "content_modified_at",
036    "created_by",
037    "modified_by",
038    "owned_by",
039    "shared_link",
040    "parent",
041    "item_status",
042    "version_number",
043    "comment_count",
044    "permissions",
045    "tags",
046    "lock",
047    "extension",
048    "is_package",
049    "folder_upload_email",
050    "item_collection",
051    "sync_state",
052    "has_collaborations",
053    "can_non_owners_invite",
054    "file_version",
055    "collections",
056    "expires_at"
057  };
058  /** Shared Item URL Template. */
059  public static final URLTemplate SHARED_ITEM_URL_TEMPLATE = new URLTemplate("shared_items");
060
061  /** Url template for operations with watermarks. */
062  public static final URLTemplate WATERMARK_URL_TEMPLATE = new URLTemplate("/watermark");
063
064  /**
065   * Constructs a BoxItem for an item with a given ID.
066   *
067   * @param api the API connection to be used by the item.
068   * @param id the ID of the item.
069   */
070  public BoxItem(BoxAPIConnection api, String id) {
071    super(api, id);
072  }
073
074  /**
075   * Gets an item that was shared with a shared link.
076   *
077   * @param api the API connection to be used by the shared item.
078   * @param sharedLink the shared link to the item.
079   * @return info about the shared item.
080   */
081  public static BoxItem.Info getSharedItem(BoxAPIConnection api, String sharedLink) {
082    return getSharedItem(api, sharedLink, null);
083  }
084
085  /**
086   * Gets an item that was shared with a password-protected shared link.
087   *
088   * @param api the API connection to be used by the shared item.
089   * @param sharedLink the shared link to the item.
090   * @param password the password for the shared link. Use `null` if shared link has no password.
091   * @param fields the fields to retrieve.
092   * @return info about the shared item.
093   */
094  public static BoxItem.Info getSharedItem(
095      BoxAPIConnection api, String sharedLink, String password, String... fields) {
096    QueryStringBuilder builder = new QueryStringBuilder();
097    if (fields.length > 0) {
098      builder.appendParam("fields", fields);
099    }
100    URL url = SHARED_ITEM_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), builder.toString());
101    BoxJSONRequest request = new BoxJSONRequest(api, url, "GET");
102
103    request.addHeader("BoxApi", BoxSharedLink.getSharedLinkHeaderValue(sharedLink, password));
104
105    try (BoxJSONResponse response = request.send()) {
106      JsonObject json = Json.parse(response.getJSON()).asObject();
107      return (BoxItem.Info) BoxResource.parseInfo(api, json);
108    }
109  }
110
111  /** @return URL for the current object, constructed as base URL pus an item specifier. */
112  protected URL getItemURL() {
113    return new URLTemplate("").build(this.getAPI().getBaseURL());
114  }
115
116  /**
117   * Used to retrieve the watermark for the item. If the item does not have a watermark applied to
118   * it, a 404 Not Found will be returned by API.
119   *
120   * @param itemUrl url template for the item.
121   * @param fields the fields to retrieve.
122   * @return the watermark associated with the item.
123   */
124  protected BoxWatermark getWatermark(URLTemplate itemUrl, String... fields) {
125    URL watermarkUrl = itemUrl.build(this.getAPI().getBaseURL(), this.getID());
126    QueryStringBuilder builder = new QueryStringBuilder();
127    if (fields.length > 0) {
128      builder.appendParam("fields", fields);
129    }
130    URL url = WATERMARK_URL_TEMPLATE.buildWithQuery(watermarkUrl.toString(), builder.toString());
131    BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
132    try (BoxJSONResponse response = request.send()) {
133      return new BoxWatermark(response.getJSON());
134    }
135  }
136
137  /**
138   * Used to apply or update the watermark for the item.
139   *
140   * @param itemUrl url template for the item.
141   * @param imprint the value must be "default", as custom watermarks is not yet supported.
142   * @return the watermark associated with the item.
143   */
144  protected BoxWatermark applyWatermark(URLTemplate itemUrl, String imprint) {
145    URL watermarkUrl = itemUrl.build(this.getAPI().getBaseURL(), this.getID());
146    URL url = WATERMARK_URL_TEMPLATE.build(watermarkUrl.toString());
147    BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
148    JsonObject body =
149        new JsonObject()
150            .add(
151                BoxWatermark.WATERMARK_JSON_KEY,
152                new JsonObject().add(BoxWatermark.WATERMARK_IMPRINT_JSON_KEY, imprint));
153    request.setBody(body.toString());
154    try (BoxJSONResponse response = request.send()) {
155      return new BoxWatermark(response.getJSON());
156    }
157  }
158
159  /**
160   * Removes a watermark from the item. If the item did not have a watermark applied to it, a 404
161   * Not Found will be returned by API.
162   *
163   * @param itemUrl url template for the item.
164   */
165  protected void removeWatermark(URLTemplate itemUrl) {
166    URL watermarkUrl = itemUrl.build(this.getAPI().getBaseURL(), this.getID());
167    URL url = WATERMARK_URL_TEMPLATE.build(watermarkUrl.toString());
168    BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
169    request.send().close();
170  }
171
172  /**
173   * Copies this item to another folder.
174   *
175   * @param destination the destination folder.
176   * @return info about the copied item.
177   */
178  public abstract BoxItem.Info copy(BoxFolder destination);
179
180  /**
181   * Copies this item to another folder and gives it a new name. If the destination is the same
182   * folder as the item's current parent, then newName must be a new, unique name.
183   *
184   * @param destination the destination folder.
185   * @param newName a new name for the copied item.
186   * @return info about the copied item.
187   */
188  public abstract BoxItem.Info copy(BoxFolder destination, String newName);
189
190  /**
191   * Moves this item to another folder.
192   *
193   * @param destination the destination folder.
194   * @return info about the moved item.
195   */
196  public abstract BoxItem.Info move(BoxFolder destination);
197
198  /**
199   * Moves this item to another folder and gives it a new name.
200   *
201   * @param destination the destination folder.
202   * @param newName a new name for the moved item.
203   * @return info about the moved item.
204   */
205  public abstract BoxItem.Info move(BoxFolder destination, String newName);
206
207  /**
208   * Gets information about this item that's limited to a list of specified fields.
209   *
210   * @param fields the fields to retrieve.
211   * @return info about this item containing only the specified fields.
212   */
213  public abstract BoxItem.Info getInfo(String... fields);
214
215  /**
216   * Sets the collections that this item belongs to.
217   *
218   * @param collections the collections that this item should belong to.
219   * @return info about the item, including the collections it belongs to.
220   */
221  public abstract BoxItem.Info setCollections(BoxCollection... collections);
222
223  /** Contains information about a BoxItem. */
224  public abstract class Info extends BoxResource.Info {
225    private String type;
226    private String sequenceID;
227    private String etag;
228    private String name;
229    private Date createdAt;
230    private Date modifiedAt;
231    private String description;
232    private long size;
233    private List<BoxFolder.Info> pathCollection;
234    private BoxUser.Info createdBy;
235    private BoxUser.Info modifiedBy;
236    private Date trashedAt;
237    private Date purgedAt;
238    private Date contentCreatedAt;
239    private Date contentModifiedAt;
240    private BoxUser.Info ownedBy;
241    private BoxSharedLink sharedLink;
242    private List<String> tags;
243    private BoxFolder.Info parent;
244    private String itemStatus;
245    private Date expiresAt;
246    private Set<BoxCollection.Info> collections;
247    private String downloadUrl;
248
249    /** Constructs an empty Info object. */
250    public Info() {
251      super();
252    }
253
254    /**
255     * Constructs an Info object by parsing information from a JSON string.
256     *
257     * @param json the JSON string to parse.
258     */
259    public Info(String json) {
260      super(json);
261    }
262
263    /**
264     * Constructs an Info object using an already parsed JSON object.
265     *
266     * @param jsonObject the parsed JSON object.
267     */
268    Info(JsonObject jsonObject) {
269      super(jsonObject);
270    }
271
272    /**
273     * Gets the item type.
274     *
275     * @return the item's type.
276     */
277    public String getType() {
278      return this.type;
279    }
280
281    /**
282     * Gets a unique string identifying the version of the item.
283     *
284     * @return a unique string identifying the version of the item.
285     */
286    public String getEtag() {
287      return this.etag;
288    }
289
290    /**
291     * Gets the name of the item.
292     *
293     * @return the name of the item.
294     */
295    public String getName() {
296      return this.name;
297    }
298
299    /**
300     * Sets the name of the item.
301     *
302     * @param name the new name of the item.
303     */
304    public void setName(String name) {
305      this.name = name;
306      this.addPendingChange("name", name);
307    }
308
309    /**
310     * Gets the time the item was created.
311     *
312     * @return the time the item was created.
313     */
314    public Date getCreatedAt() {
315      return this.createdAt;
316    }
317
318    /**
319     * Gets the time the item was last modified.
320     *
321     * @return the time the item was last modified.
322     */
323    public Date getModifiedAt() {
324      return this.modifiedAt;
325    }
326
327    /**
328     * Gets the description of the item.
329     *
330     * @return the description of the item.
331     */
332    public String getDescription() {
333      return this.description;
334    }
335
336    /**
337     * Sets the description of the item.
338     *
339     * @param description the new description of the item.
340     */
341    public void setDescription(String description) {
342      this.description = description;
343      this.addPendingChange("description", description);
344    }
345
346    /**
347     * Gets the size of the item in bytes.
348     *
349     * @return the size of the item in bytes.
350     */
351    public long getSize() {
352      return this.size;
353    }
354
355    /**
356     * Gets the path of folders to the item, starting at the root.
357     *
358     * @return the path of folders to the item.
359     */
360    public List<BoxFolder.Info> getPathCollection() {
361      return this.pathCollection;
362    }
363
364    /**
365     * Gets info about the user who created the item.
366     *
367     * @return info about the user who created the item.
368     */
369    public BoxUser.Info getCreatedBy() {
370      return this.createdBy;
371    }
372
373    /**
374     * Gets info about the user who last modified the item.
375     *
376     * @return info about the user who last modified the item.
377     */
378    public BoxUser.Info getModifiedBy() {
379      return this.modifiedBy;
380    }
381
382    /**
383     * Gets the time that the item was trashed.
384     *
385     * @return the time that the item was trashed.
386     */
387    public Date getTrashedAt() {
388      return this.trashedAt;
389    }
390
391    /**
392     * Gets the time that the item was purged from the trash.
393     *
394     * @return the time that the item was purged from the trash.
395     */
396    public Date getPurgedAt() {
397      return this.purgedAt;
398    }
399
400    /**
401     * Gets the time that the item was created according to the uploader.
402     *
403     * @return the time that the item was created according to the uploader.
404     */
405    public Date getContentCreatedAt() {
406      return this.contentCreatedAt;
407    }
408
409    /**
410     * Gets the time that the item was last modified according to the uploader.
411     *
412     * @return the time that the item was last modified according to the uploader.
413     */
414    public Date getContentModifiedAt() {
415      return this.contentModifiedAt;
416    }
417
418    /**
419     * Gets the expires at time for this item.
420     *
421     * @return the time that the item will expire at.
422     */
423    public Date getExpiresAt() {
424      return this.expiresAt;
425    }
426
427    /**
428     * Gets info about the user who owns the item.
429     *
430     * @return info about the user who owns the item.
431     */
432    public BoxUser.Info getOwnedBy() {
433      return this.ownedBy;
434    }
435
436    /**
437     * Gets the shared link for the item.
438     *
439     * @return the shared link for the item.
440     */
441    public BoxSharedLink getSharedLink() {
442      return this.sharedLink;
443    }
444
445    /**
446     * Sets a shared link for the item.
447     *
448     * @param sharedLink the shared link for the item.
449     */
450    public void setSharedLink(BoxSharedLink sharedLink) {
451      this.removeChildObject("shared_link");
452      this.sharedLink = sharedLink;
453      this.addChildObject("shared_link", sharedLink);
454    }
455
456    /** Removes the shared link for the item. */
457    public void removeSharedLink() {
458      this.addChildObject("shared_link", null);
459    }
460
461    /**
462     * Gets a unique ID for use with the {@link EventStream}.
463     *
464     * @return a unique ID for use with the EventStream.
465     */
466    public String getSequenceID() {
467      return this.sequenceID;
468    }
469
470    /**
471     * Gets a list of all the tags applied to the item.
472     *
473     * <p>Note that this field isn't populated by default and must be specified as a field parameter
474     * when getting Info about the item.
475     *
476     * @return a list of all the tags applied to the item.
477     */
478    public List<String> getTags() {
479      return this.tags;
480    }
481
482    /**
483     * Sets the tags for an item.
484     *
485     * @param tags The new tags for the item.
486     */
487    public void setTags(List<String> tags) {
488      this.tags = tags;
489      JsonArray tagsJSON = new JsonArray();
490      for (String tag : tags) {
491        tagsJSON.add(tag);
492      }
493      this.addPendingChange("tags", tagsJSON);
494    }
495
496    /**
497     * Gets info about the parent folder of the item.
498     *
499     * @return info about the parent folder of the item.
500     */
501    public BoxFolder.Info getParent() {
502      return this.parent;
503    }
504
505    /**
506     * Gets the status of the item.
507     *
508     * @return the status of the item.
509     */
510    public String getItemStatus() {
511      return this.itemStatus;
512    }
513
514    /**
515     * Gets info about the collections that this item belongs to.
516     *
517     * @return info about the collections that this item belongs to.
518     */
519    public Iterable<BoxCollection.Info> getCollections() {
520      return this.collections;
521    }
522
523    /**
524     * * Gets URL that can be used to download the file.
525     *
526     * @return
527     */
528    public String getDownloadUrl() {
529      return this.downloadUrl;
530    }
531
532    /**
533     * Sets the collections that this item belongs to.
534     *
535     * @param collections the new list of collections that this item should belong to.
536     */
537    public void setCollections(Iterable<BoxCollection> collections) {
538      if (this.collections == null) {
539        this.collections = new HashSet<>();
540      } else {
541        this.collections.clear();
542      }
543
544      JsonArray jsonArray = new JsonArray();
545      for (BoxCollection collection : collections) {
546        JsonObject jsonObject = new JsonObject();
547        jsonObject.add("id", collection.getID());
548        jsonArray.add(jsonObject);
549        this.collections.add(collection.new Info());
550      }
551      this.addPendingChange("collections", jsonArray);
552    }
553
554    @Override
555    protected void parseJSONMember(JsonObject.Member member) {
556      super.parseJSONMember(member);
557      JsonValue value = member.getValue();
558      String memberName = member.getName();
559
560      try {
561        switch (memberName) {
562          case "sequence_id":
563            this.sequenceID = value.asString();
564            break;
565          case "type":
566            this.type = value.asString();
567            break;
568          case "etag":
569            this.etag = value.asString();
570            break;
571          case "name":
572            this.name = value.asString();
573            break;
574          case "created_at":
575            this.createdAt = BoxDateFormat.parse(value.asString());
576            break;
577          case "modified_at":
578            this.modifiedAt = BoxDateFormat.parse(value.asString());
579            break;
580          case "description":
581            this.description = value.asString();
582            break;
583          case "size":
584            this.size = Double.valueOf(value.toString()).longValue();
585            break;
586          case "trashed_at":
587            this.trashedAt = BoxDateFormat.parse(value.asString());
588            break;
589          case "purged_at":
590            this.purgedAt = BoxDateFormat.parse(value.asString());
591            break;
592          case "content_created_at":
593            this.contentCreatedAt = BoxDateFormat.parse(value.asString());
594            break;
595          case "content_modified_at":
596            this.contentModifiedAt = BoxDateFormat.parse(value.asString());
597            break;
598          case "expires_at":
599            this.expiresAt = BoxDateFormat.parse(value.asString());
600            break;
601          case "path_collection":
602            this.pathCollection = this.parsePathCollection(value.asObject());
603            break;
604          case "created_by":
605            this.createdBy = this.parseUserInfo(value.asObject());
606            break;
607          case "modified_by":
608            this.modifiedBy = this.parseUserInfo(value.asObject());
609            break;
610          case "owned_by":
611            this.ownedBy = this.parseUserInfo(value.asObject());
612            break;
613          case "shared_link":
614            if (this.sharedLink == null) {
615              this.setSharedLink(new BoxSharedLink(value.asObject()));
616            } else {
617              this.sharedLink.update(value.asObject());
618            }
619            break;
620          case "tags":
621            this.tags = this.parseTags(value.asArray());
622            break;
623          case "parent":
624            JsonObject parentObject = value.asObject();
625            if (this.parent == null) {
626              String id = parentObject.get("id").asString();
627              BoxFolder parentFolder = new BoxFolder(getAPI(), id);
628              this.parent = parentFolder.new Info(parentObject);
629            } else {
630              this.parent.update(parentObject);
631            }
632            break;
633          case "item_status":
634            this.itemStatus = value.asString();
635            break;
636          case "collections":
637            if (this.collections == null) {
638              this.collections = new HashSet<>();
639            } else {
640              this.collections.clear();
641            }
642
643            BoxAPIConnection api = getAPI();
644            JsonArray jsonArray = value.asArray();
645            for (JsonValue arrayValue : jsonArray) {
646              JsonObject jsonObject = arrayValue.asObject();
647              String id = jsonObject.get("id").asString();
648              BoxCollection collection = new BoxCollection(api, id);
649              BoxCollection.Info collectionInfo = collection.new Info(jsonObject);
650              this.collections.add(collectionInfo);
651            }
652            break;
653          case "download_url":
654            this.downloadUrl = value.asString();
655            break;
656          default:
657            break;
658        }
659      } catch (Exception e) {
660        throw new BoxDeserializationException(memberName, value.toString(), e);
661      }
662    }
663
664    private List<BoxFolder.Info> parsePathCollection(JsonObject jsonObject) {
665      int count = jsonObject.get("total_count").asInt();
666      List<BoxFolder.Info> pathCollection = new ArrayList<>(count);
667      JsonArray entries = jsonObject.get("entries").asArray();
668      for (JsonValue value : entries) {
669        JsonObject entry = value.asObject();
670        String id = entry.get("id").asString();
671        BoxFolder folder = new BoxFolder(getAPI(), id);
672        pathCollection.add(folder.new Info(entry));
673      }
674
675      return pathCollection;
676    }
677
678    private BoxUser.Info parseUserInfo(JsonObject jsonObject) {
679      String userID = jsonObject.get("id").asString();
680      BoxUser user = new BoxUser(getAPI(), userID);
681      return user.new Info(jsonObject);
682    }
683
684    private List<String> parseTags(JsonArray jsonArray) {
685      List<String> tags = new ArrayList<>(jsonArray.size());
686      for (JsonValue value : jsonArray) {
687        tags.add(value.asString());
688      }
689
690      return tags;
691    }
692  }
693}