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