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