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