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