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