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