001package com.box.sdk;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.net.URL;
006import java.util.ArrayList;
007import java.util.Collection;
008import java.util.Date;
009import java.util.EnumSet;
010import java.util.Iterator;
011import java.util.concurrent.TimeUnit;
012
013import com.eclipsesource.json.JsonArray;
014import com.eclipsesource.json.JsonObject;
015import com.eclipsesource.json.JsonValue;
016
017/**
018 * Represents a folder on Box. This class can be used to iterate through a folder's contents, collaborate a folder with
019 * another user or group, and perform other common folder operations (move, copy, delete, etc.).
020 *
021 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked
022 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error
023 * handling for errors related to the Box REST API, you should capture this exception explicitly.</p>
024 */
025@BoxResourceType("folder")
026public class BoxFolder extends BoxItem implements Iterable<BoxItem.Info> {
027    /**
028     * An array of all possible folder fields that can be requested when calling {@link #getInfo()}.
029     */
030    public static final String[] ALL_FIELDS = {"type", "id", "sequence_id", "etag", "name", "created_at", "modified_at",
031        "description", "size", "path_collection", "created_by", "modified_by", "trashed_at", "purged_at",
032        "content_created_at", "content_modified_at", "owned_by", "shared_link", "folder_upload_email", "parent",
033        "item_status", "item_collection", "sync_state", "has_collaborations", "permissions", "tags",
034        "can_non_owners_invite", "collections", "watermark_info"};
035
036    private static final URLTemplate CREATE_FOLDER_URL = new URLTemplate("folders");
037    private static final URLTemplate CREATE_WEB_LINK_URL = new URLTemplate("web_links");
038    private static final URLTemplate COPY_FOLDER_URL = new URLTemplate("folders/%s/copy");
039    private static final URLTemplate DELETE_FOLDER_URL = new URLTemplate("folders/%s?recursive=%b");
040    private static final URLTemplate FOLDER_INFO_URL_TEMPLATE = new URLTemplate("folders/%s");
041    private static final URLTemplate UPLOAD_FILE_URL = new URLTemplate("files/content");
042    private static final URLTemplate ADD_COLLABORATION_URL = new URLTemplate("collaborations");
043    private static final URLTemplate GET_COLLABORATIONS_URL = new URLTemplate("folders/%s/collaborations");
044    private static final URLTemplate GET_ITEMS_URL = new URLTemplate("folders/%s/items/");
045    private static final URLTemplate SEARCH_URL_TEMPLATE = new URLTemplate("search");
046    private static final URLTemplate METADATA_URL_TEMPLATE = new URLTemplate("folders/%s/metadata/%s/%s");
047    private static final URLTemplate UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/upload_sessions");
048
049    /**
050     * Constructs a BoxFolder for a folder with a given ID.
051     * @param  api the API connection to be used by the folder.
052     * @param  id  the ID of the folder.
053     */
054    public BoxFolder(BoxAPIConnection api, String id) {
055        super(api, id);
056    }
057
058    /**
059     * {@inheritDoc}
060     */
061    @Override
062    protected URL getItemURL() {
063        return FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
064    }
065
066    /**
067     * Gets the current user's root folder.
068     * @param  api the API connection to be used by the folder.
069     * @return     the user's root folder.
070     */
071    public static BoxFolder getRootFolder(BoxAPIConnection api) {
072        return new BoxFolder(api, "0");
073    }
074
075    /**
076     * Adds a collaborator to this folder.
077     * @param  collaborator the collaborator to add.
078     * @param  role         the role of the collaborator.
079     * @return              info about the new collaboration.
080     */
081    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role) {
082        JsonObject accessibleByField = new JsonObject();
083        accessibleByField.add("id", collaborator.getID());
084
085        if (collaborator instanceof BoxUser) {
086            accessibleByField.add("type", "user");
087        } else if (collaborator instanceof BoxGroup) {
088            accessibleByField.add("type", "group");
089        } else {
090            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
091        }
092
093        return this.collaborate(accessibleByField, role);
094    }
095
096    /**
097     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
098     * account.
099     * @param  email the email address of the collaborator to add.
100     * @param  role  the role of the collaborator.
101     * @return       info about the new collaboration.
102     */
103    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role) {
104        JsonObject accessibleByField = new JsonObject();
105        accessibleByField.add("login", email);
106        accessibleByField.add("type", "user");
107
108        return this.collaborate(accessibleByField, role);
109    }
110
111    private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role) {
112        BoxAPIConnection api = this.getAPI();
113        URL url = ADD_COLLABORATION_URL.build(api.getBaseURL());
114
115        JsonObject itemField = new JsonObject();
116        itemField.add("id", this.getID());
117        itemField.add("type", "folder");
118
119        JsonObject requestJSON = new JsonObject();
120        requestJSON.add("item", itemField);
121        requestJSON.add("accessible_by", accessibleByField);
122        requestJSON.add("role", role.toJSONString());
123
124        BoxJSONRequest request = new BoxJSONRequest(api, url, "POST");
125        request.setBody(requestJSON.toString());
126        BoxJSONResponse response = (BoxJSONResponse) request.send();
127        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
128
129        BoxCollaboration newCollaboration = new BoxCollaboration(api, responseJSON.get("id").asString());
130        BoxCollaboration.Info info = newCollaboration.new Info(responseJSON);
131        return info;
132    }
133    /**
134     * Adds a collaborator to this folder.
135     * @param  collaborator the collaborator to add.
136     * @param  role         the role of the collaborator.
137     * @param  notify       the user/group should receive email notification of the collaboration or not.
138     * @param  canViewPath  the view path collaboration feature is enabled or not.
139     * View path collaborations allow the invitee to see the entire ancestral path to the associated folder.
140     * The user will not gain privileges in any ancestral folder.
141     * @return              info about the new collaboration.
142     */
143    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role,
144                                             Boolean notify, Boolean canViewPath) {
145        JsonObject accessibleByField = new JsonObject();
146        accessibleByField.add("id", collaborator.getID());
147
148        if (collaborator instanceof BoxUser) {
149            accessibleByField.add("type", "user");
150        } else if (collaborator instanceof BoxGroup) {
151            accessibleByField.add("type", "group");
152        } else {
153            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
154        }
155
156        return this.collaborate(accessibleByField, role, notify, canViewPath);
157    }
158
159    /**
160     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
161     * account.
162     * @param  email the email address of the collaborator to add.
163     * @param  role  the role of the collaborator.
164     * @param  notify       the user/group should receive email notification of the collaboration or not.
165     * @param  canViewPath  the view path collaboration feature is enabled or not.
166     * View path collaborations allow the invitee to see the entire ancestral path to the associated folder.
167     * The user will not gain privileges in any ancestral folder.
168     * @return       info about the new collaboration.
169     */
170    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role,
171                                             Boolean notify, Boolean canViewPath) {
172        JsonObject accessibleByField = new JsonObject();
173        accessibleByField.add("login", email);
174        accessibleByField.add("type", "user");
175
176        return this.collaborate(accessibleByField, role, notify, canViewPath);
177    }
178
179    private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role,
180                                              Boolean notify, Boolean canViewPath) {
181        BoxAPIConnection api = this.getAPI();
182        URL url = ADD_COLLABORATION_URL.build(api.getBaseURL());
183
184        JsonObject itemField = new JsonObject();
185        itemField.add("id", this.getID());
186        itemField.add("type", "folder");
187
188        JsonObject requestJSON = new JsonObject();
189        requestJSON.add("item", itemField);
190        requestJSON.add("accessible_by", accessibleByField);
191        requestJSON.add("role", role.toJSONString());
192        if (canViewPath != null) {
193            requestJSON.add("can_view_path", canViewPath.booleanValue());
194        }
195
196        BoxJSONRequest request = new BoxJSONRequest(api, url, "POST");
197        if (notify != null) {
198            request.addHeader("notify", notify.toString());
199        }
200
201        request.setBody(requestJSON.toString());
202        BoxJSONResponse response = (BoxJSONResponse) request.send();
203        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
204
205        BoxCollaboration newCollaboration = new BoxCollaboration(api, responseJSON.get("id").asString());
206        BoxCollaboration.Info info = newCollaboration.new Info(responseJSON);
207        return info;
208    }
209
210    @Override
211    public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate,
212        BoxSharedLink.Permissions permissions) {
213
214        BoxSharedLink sharedLink = new BoxSharedLink(access, unshareDate, permissions);
215        Info info = new Info();
216        info.setSharedLink(sharedLink);
217
218        this.updateInfo(info);
219        return info.getSharedLink();
220    }
221
222    /**
223     * Gets information about all of the collaborations for this folder.
224     * @return a collection of information about the collaborations for this folder.
225     */
226    public Collection<BoxCollaboration.Info> getCollaborations() {
227        BoxAPIConnection api = this.getAPI();
228        URL url = GET_COLLABORATIONS_URL.build(api.getBaseURL(), this.getID());
229
230        BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
231        BoxJSONResponse response = (BoxJSONResponse) request.send();
232        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
233
234        int entriesCount = responseJSON.get("total_count").asInt();
235        Collection<BoxCollaboration.Info> collaborations = new ArrayList<BoxCollaboration.Info>(entriesCount);
236        JsonArray entries = responseJSON.get("entries").asArray();
237        for (JsonValue entry : entries) {
238            JsonObject entryObject = entry.asObject();
239            BoxCollaboration collaboration = new BoxCollaboration(api, entryObject.get("id").asString());
240            BoxCollaboration.Info info = collaboration.new Info(entryObject);
241            collaborations.add(info);
242        }
243
244        return collaborations;
245    }
246
247
248
249    @Override
250    public BoxFolder.Info getInfo() {
251        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
252        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
253        BoxJSONResponse response = (BoxJSONResponse) request.send();
254        return new Info(response.getJSON());
255    }
256
257    @Override
258    public BoxFolder.Info getInfo(String... fields) {
259        String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
260        URL url = FOLDER_INFO_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
261
262        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
263        BoxJSONResponse response = (BoxJSONResponse) request.send();
264        return new Info(response.getJSON());
265    }
266
267    /**
268     * Updates the information about this folder with any info fields that have been modified locally.
269     * @param info the updated info.
270     */
271    public void updateInfo(BoxFolder.Info info) {
272        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
273        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
274        request.setBody(info.getPendingChanges());
275        BoxJSONResponse response = (BoxJSONResponse) request.send();
276        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
277        info.update(jsonObject);
278    }
279
280    @Override
281    public BoxFolder.Info copy(BoxFolder destination) {
282        return this.copy(destination, null);
283    }
284
285    @Override
286    public BoxFolder.Info copy(BoxFolder destination, String newName) {
287        URL url = COPY_FOLDER_URL.build(this.getAPI().getBaseURL(), this.getID());
288        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
289
290        JsonObject parent = new JsonObject();
291        parent.add("id", destination.getID());
292
293        JsonObject copyInfo = new JsonObject();
294        copyInfo.add("parent", parent);
295        if (newName != null) {
296            copyInfo.add("name", newName);
297        }
298
299        request.setBody(copyInfo.toString());
300        BoxJSONResponse response = (BoxJSONResponse) request.send();
301        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
302        BoxFolder copiedFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
303        return copiedFolder.new Info(responseJSON);
304    }
305
306    /**
307     * Creates a new child folder inside this folder.
308     * @param  name the new folder's name.
309     * @return      the created folder's info.
310     */
311    public BoxFolder.Info createFolder(String name) {
312        JsonObject parent = new JsonObject();
313        parent.add("id", this.getID());
314
315        JsonObject newFolder = new JsonObject();
316        newFolder.add("name", name);
317        newFolder.add("parent", parent);
318
319        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), CREATE_FOLDER_URL.build(this.getAPI().getBaseURL()),
320            "POST");
321        request.setBody(newFolder.toString());
322        BoxJSONResponse response = (BoxJSONResponse) request.send();
323        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
324
325        BoxFolder createdFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
326        return createdFolder.new Info(responseJSON);
327    }
328
329    /**
330     * Deletes this folder, optionally recursively deleting all of its contents.
331     * @param recursive true to recursively delete this folder's contents; otherwise false.
332     */
333    public void delete(boolean recursive) {
334        URL url = DELETE_FOLDER_URL.build(this.getAPI().getBaseURL(), this.getID(), recursive);
335        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
336        BoxAPIResponse response = request.send();
337        response.disconnect();
338    }
339
340    @Override
341    public BoxItem.Info move(BoxFolder destination) {
342        return this.move(destination, null);
343    }
344
345    @Override
346    public BoxItem.Info move(BoxFolder destination, String newName) {
347        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
348        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
349
350        JsonObject parent = new JsonObject();
351        parent.add("id", destination.getID());
352
353        JsonObject updateInfo = new JsonObject();
354        updateInfo.add("parent", parent);
355        if (newName != null) {
356            updateInfo.add("name", newName);
357        }
358
359        request.setBody(updateInfo.toString());
360        BoxJSONResponse response = (BoxJSONResponse) request.send();
361        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
362        BoxFolder movedFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
363        return movedFolder.new Info(responseJSON);
364    }
365
366    /**
367     * Renames this folder.
368     * @param newName the new name of the folder.
369     */
370    public void rename(String newName) {
371        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
372        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
373
374        JsonObject updateInfo = new JsonObject();
375        updateInfo.add("name", newName);
376
377        request.setBody(updateInfo.toString());
378        BoxJSONResponse response = (BoxJSONResponse) request.send();
379        response.getJSON();
380    }
381
382    /**
383     * Checks if the file can be successfully uploaded by using the preflight check.
384     * @param  name        the name to give the uploaded file.
385     * @param  fileSize    the size of the file used for account capacity calculations.
386     */
387    public void canUpload(String name, long fileSize) {
388        URL url = UPLOAD_FILE_URL.build(this.getAPI().getBaseURL());
389        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
390
391        JsonObject parent = new JsonObject();
392        parent.add("id", this.getID());
393
394        JsonObject preflightInfo = new JsonObject();
395        preflightInfo.add("parent", parent);
396        preflightInfo.add("name", name);
397
398        preflightInfo.add("size", fileSize);
399
400        request.setBody(preflightInfo.toString());
401        BoxAPIResponse response = request.send();
402        response.disconnect();
403    }
404
405    /**
406     * Uploads a new file to this folder.
407     * @param  fileContent a stream containing the contents of the file to upload.
408     * @param  name        the name to give the uploaded file.
409     * @return             the uploaded file's info.
410     */
411    public BoxFile.Info uploadFile(InputStream fileContent, String name) {
412        FileUploadParams uploadInfo = new FileUploadParams()
413            .setContent(fileContent)
414            .setName(name);
415        return this.uploadFile(uploadInfo);
416    }
417
418    /**
419     * Uploads a new file to this folder while reporting the progress to a ProgressListener.
420     * @param  fileContent a stream containing the contents of the file to upload.
421     * @param  name        the name to give the uploaded file.
422     * @param  fileSize    the size of the file used for determining the progress of the upload.
423     * @param  listener    a listener for monitoring the upload's progress.
424     * @return             the uploaded file's info.
425     */
426    public BoxFile.Info uploadFile(InputStream fileContent, String name, long fileSize, ProgressListener listener) {
427        FileUploadParams uploadInfo = new FileUploadParams()
428            .setContent(fileContent)
429            .setName(name)
430            .setSize(fileSize)
431            .setProgressListener(listener);
432        return this.uploadFile(uploadInfo);
433    }
434
435    /**
436     * Uploads a new file to this folder with custom upload parameters.
437     * @param  uploadParams the custom upload parameters.
438     * @return              the uploaded file's info.
439     */
440    public BoxFile.Info uploadFile(FileUploadParams uploadParams) {
441        URL uploadURL = UPLOAD_FILE_URL.build(this.getAPI().getBaseUploadURL());
442        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
443
444        JsonObject fieldJSON = new JsonObject();
445        JsonObject parentIdJSON = new JsonObject();
446        parentIdJSON.add("id", getID());
447        fieldJSON.add("name", uploadParams.getName());
448        fieldJSON.add("parent", parentIdJSON);
449
450        if (uploadParams.getCreated() != null) {
451            fieldJSON.add("content_created_at", BoxDateFormat.format(uploadParams.getCreated()));
452        }
453
454        if (uploadParams.getModified() != null) {
455            fieldJSON.add("content_modified_at", BoxDateFormat.format(uploadParams.getModified()));
456        }
457
458        request.putField("attributes", fieldJSON.toString());
459
460        if (uploadParams.getSize() > 0) {
461            request.setFile(uploadParams.getContent(), uploadParams.getName(), uploadParams.getSize());
462        } else {
463            request.setFile(uploadParams.getContent(), uploadParams.getName());
464        }
465
466        BoxJSONResponse response;
467        if (uploadParams.getProgressListener() == null) {
468            response = (BoxJSONResponse) request.send();
469        } else {
470            response = (BoxJSONResponse) request.send(uploadParams.getProgressListener());
471        }
472        JsonObject collection = JsonObject.readFrom(response.getJSON());
473        JsonArray entries = collection.get("entries").asArray();
474        JsonObject fileInfoJSON = entries.get(0).asObject();
475        String uploadedFileID = fileInfoJSON.get("id").asString();
476
477        BoxFile uploadedFile = new BoxFile(getAPI(), uploadedFileID);
478        return uploadedFile.new Info(fileInfoJSON);
479    }
480
481    /**
482     * Uploads a new weblink to this folder.
483     * @param  linkURL     the URL the weblink points to.
484     * @return             the uploaded weblink's info.
485     */
486    public BoxWebLink.Info createWebLink(URL linkURL) {
487        return this.createWebLink(null, linkURL, null);
488    }
489
490    /**
491     * Uploads a new weblink to this folder.
492     * @param  name        the filename for the weblink.
493     * @param  linkURL     the URL the weblink points to.
494     * @return             the uploaded weblink's info.
495     */
496    public BoxWebLink.Info createWebLink(String name, URL linkURL) {
497        return this.createWebLink(name, linkURL, null);
498    }
499
500    /**
501     * Uploads a new weblink to this folder.
502     * @param  linkURL     the URL the weblink points to.
503     * @param  description the weblink's description.
504     * @return             the uploaded weblink's info.
505     */
506    public BoxWebLink.Info createWebLink(URL linkURL, String description) {
507        return this.createWebLink(null, linkURL, description);
508    }
509
510    /**
511     * Uploads a new weblink to this folder.
512     * @param  name        the filename for the weblink.
513     * @param  linkURL     the URL the weblink points to.
514     * @param  description the weblink's description.
515     * @return             the uploaded weblink's info.
516     */
517    public BoxWebLink.Info createWebLink(String name, URL linkURL, String description) {
518        JsonObject parent = new JsonObject();
519        parent.add("id", this.getID());
520
521        JsonObject newWebLink = new JsonObject();
522        newWebLink.add("name", name);
523        newWebLink.add("parent", parent);
524        newWebLink.add("url", linkURL.toString());
525
526        if (description != null) {
527            newWebLink.add("description", description);
528        }
529
530        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(),
531            CREATE_WEB_LINK_URL.build(this.getAPI().getBaseURL()), "POST");
532        request.setBody(newWebLink.toString());
533        BoxJSONResponse response = (BoxJSONResponse) request.send();
534        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
535
536        BoxWebLink createdWebLink = new BoxWebLink(this.getAPI(), responseJSON.get("id").asString());
537        return createdWebLink.new Info(responseJSON);
538    }
539
540    /**
541     * Returns an iterable containing the items in this folder. Iterating over the iterable returned by this method is
542     * equivalent to iterating over this BoxFolder directly.
543     * @return an iterable containing the items in this folder.
544     */
545    public Iterable<BoxItem.Info> getChildren() {
546        return this;
547    }
548
549    /**
550     * Returns an iterable containing the items in this folder and specifies which child fields to retrieve from the
551     * API.
552     * @param  fields the fields to retrieve.
553     * @return        an iterable containing the items in this folder.
554     */
555    public Iterable<BoxItem.Info> getChildren(final String... fields) {
556        return new Iterable<BoxItem.Info>() {
557            @Override
558            public Iterator<BoxItem.Info> iterator() {
559                String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
560                URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), queryString, getID());
561                return new BoxItemIterator(getAPI(), url);
562            }
563        };
564    }
565
566    /**
567     * Retrieves a specific range of child items in this folder.
568     * @param  offset the index of the first child item to retrieve.
569     * @param  limit  the maximum number of children to retrieve after the offset.
570     * @param  fields the fields to retrieve.
571     * @return        a partial collection containing the specified range of child items.
572     */
573    public PartialCollection<BoxItem.Info> getChildrenRange(long offset, long limit, String... fields) {
574        QueryStringBuilder builder = new QueryStringBuilder()
575            .appendParam("limit", limit)
576            .appendParam("offset", offset);
577
578        if (fields.length > 0) {
579            builder.appendParam("fields", fields).toString();
580        }
581
582        URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), builder.toString(), getID());
583        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
584        BoxJSONResponse response = (BoxJSONResponse) request.send();
585        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
586
587        String totalCountString = responseJSON.get("total_count").toString();
588        long fullSize = Double.valueOf(totalCountString).longValue();
589        PartialCollection<BoxItem.Info> children = new PartialCollection<BoxItem.Info>(offset, limit, fullSize);
590        JsonArray jsonArray = responseJSON.get("entries").asArray();
591        for (JsonValue value : jsonArray) {
592            JsonObject jsonObject = value.asObject();
593            BoxItem.Info parsedItemInfo = (BoxItem.Info) BoxResource.parseInfo(this.getAPI(), jsonObject);
594            if (parsedItemInfo != null) {
595                children.add(parsedItemInfo);
596            }
597        }
598        return children;
599    }
600
601    /**
602     * Returns an iterator over the items in this folder.
603     * @return an iterator over the items in this folder.
604     */
605    @Override
606    public Iterator<BoxItem.Info> iterator() {
607        URL url = GET_ITEMS_URL.build(this.getAPI().getBaseURL(), BoxFolder.this.getID());
608        return new BoxItemIterator(BoxFolder.this.getAPI(), url);
609    }
610
611    /**
612     * Adds new {@link BoxWebHook} to this {@link BoxFolder}.
613     *
614     * @param address
615     *            {@link BoxWebHook.Info#getAddress()}
616     * @param triggers
617     *            {@link BoxWebHook.Info#getTriggers()}
618     * @return created {@link BoxWebHook.Info}
619     */
620    public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) {
621        return BoxWebHook.create(this, address, triggers);
622    }
623
624    /**
625
626     * Used to retrieve the watermark for the folder.
627     * If the folder does not have a watermark applied to it, a 404 Not Found will be returned by API.
628     * @param fields the fields to retrieve.
629     * @return the watermark associated with the folder.
630     */
631    public BoxWatermark getWatermark(String... fields) {
632        return this.getWatermark(FOLDER_INFO_URL_TEMPLATE, fields);
633    }
634
635    /**
636     * Used to apply or update the watermark for the folder.
637     * @return the watermark associated with the folder.
638     */
639    public BoxWatermark applyWatermark() {
640        return this.applyWatermark(FOLDER_INFO_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT);
641    }
642
643    /**
644     * Removes a watermark from the folder.
645     * If the folder did not have a watermark applied to it, a 404 Not Found will be returned by API.
646     */
647    public void removeWatermark() {
648        this.removeWatermark(FOLDER_INFO_URL_TEMPLATE);
649    }
650
651    /**
652     * Used to retrieve all metadata associated with the folder.
653     *
654     * @param fields the optional fields to retrieve.
655     * @return An iterable of metadata instances associated with the folder
656     */
657    public Iterable<Metadata> getAllMetadata(String... fields) {
658        return Metadata.getAllMetadata(this, fields);
659    }
660
661    /**
662     * This method is deprecated, please use the {@link BoxSearch} class instead.
663     * Searches this folder and all descendant folders using a given queryPlease use BoxSearch Instead.
664     * @param  query the search query.
665     * @return an Iterable containing the search results.
666     */
667    @Deprecated
668    public Iterable<BoxItem.Info> search(final String query) {
669        return new Iterable<BoxItem.Info>() {
670            @Override
671            public Iterator<BoxItem.Info> iterator() {
672                QueryStringBuilder builder = new QueryStringBuilder();
673                builder.appendParam("query", query);
674                builder.appendParam("ancestor_folder_ids", getID());
675
676                URL url = SEARCH_URL_TEMPLATE.buildWithQuery(getAPI().getBaseURL(), builder.toString());
677                return new BoxItemIterator(getAPI(), url);
678            }
679        };
680    }
681
682    @Override
683    public BoxFolder.Info setCollections(BoxCollection... collections) {
684        JsonArray jsonArray = new JsonArray();
685        for (BoxCollection collection : collections) {
686            JsonObject collectionJSON = new JsonObject();
687            collectionJSON.add("id", collection.getID());
688            jsonArray.add(collectionJSON);
689        }
690        JsonObject infoJSON = new JsonObject();
691        infoJSON.add("collections", jsonArray);
692
693        String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString();
694        URL url = FOLDER_INFO_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
695        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
696        request.setBody(infoJSON.toString());
697        BoxJSONResponse response = (BoxJSONResponse) request.send();
698        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
699        return new Info(jsonObject);
700    }
701
702    /**
703     * Creates global property metadata on this folder.
704     * @param   metadata    the new metadata values.
705     * @return              the metadata returned from the server.
706     */
707    public Metadata createMetadata(Metadata metadata) {
708        return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata);
709    }
710
711    /**
712     * Creates metadata on this folder using a specified template.
713     * @param   templateName    the name of the metadata template.
714     * @param   metadata        the new metadata values.
715     * @return                  the metadata returned from the server.
716     */
717    public Metadata createMetadata(String templateName, Metadata metadata) {
718        String scope = Metadata.scopeBasedOnType(templateName);
719        return this.createMetadata(templateName, scope, metadata);
720    }
721
722    /**
723     * Creates metadata on this folder using a specified scope and template.
724     * @param   templateName    the name of the metadata template.
725     * @param   scope           the scope of the template (usually "global" or "enterprise").
726     * @param   metadata        the new metadata values.
727     * @return                  the metadata returned from the server.
728     */
729    public Metadata createMetadata(String templateName, String scope, Metadata metadata) {
730        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
731        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "POST");
732        request.addHeader("Content-Type", "application/json");
733        request.setBody(metadata.toString());
734        BoxJSONResponse response = (BoxJSONResponse) request.send();
735        return new Metadata(JsonObject.readFrom(response.getJSON()));
736    }
737
738    /**
739     * Gets the global properties metadata on this folder.
740     * @return the metadata returned from the server.
741     */
742    public Metadata getMetadata() {
743        return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE);
744    }
745
746    /**
747     * Gets the metadata on this folder associated with a specified template.
748     * @param   templateName    the metadata template type name.
749     * @return                  the metadata returned from the server.
750     */
751    public Metadata getMetadata(String templateName) {
752        String scope = Metadata.scopeBasedOnType(templateName);
753        return this.getMetadata(templateName, scope);
754    }
755
756    /**
757     * Gets the metadata on this folder associated with a specified scope and template.
758     * @param   templateName    the metadata template type name.
759     * @param   scope           the scope of the template (usually "global" or "enterprise").
760     * @return                  the metadata returned from the server.
761     */
762    public Metadata getMetadata(String templateName, String scope) {
763        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
764        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
765        BoxJSONResponse response = (BoxJSONResponse) request.send();
766        return new Metadata(JsonObject.readFrom(response.getJSON()));
767    }
768
769    /**
770     * Updates the global properties metadata on this folder.
771     * @param   metadata    the new metadata values.
772     * @return              the metadata returned from the server.
773     */
774    public Metadata updateMetadata(Metadata metadata) {
775        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), metadata.getScope(),
776            metadata.getTemplateName());
777        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
778        request.addHeader("Content-Type", "application/json-patch+json");
779        request.setBody(metadata.getPatch());
780        BoxJSONResponse response = (BoxJSONResponse) request.send();
781        return new Metadata(JsonObject.readFrom(response.getJSON()));
782    }
783
784    /**
785     * Deletes the global properties metadata on this folder.
786     */
787    public void deleteMetadata() {
788        this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE);
789    }
790
791    /**
792     * Deletes the metadata on this folder associated with a specified template.
793     * @param templateName the metadata template type name.
794     */
795    public void deleteMetadata(String templateName) {
796        String scope = Metadata.scopeBasedOnType(templateName);
797        this.deleteMetadata(templateName, scope);
798    }
799
800    /**
801     * Deletes the metadata on this folder associated with a specified scope and template.
802     * @param   templateName    the metadata template type name.
803     * @param   scope           the scope of the template (usually "global" or "enterprise").
804     */
805    public void deleteMetadata(String templateName, String scope) {
806        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
807        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
808        BoxAPIResponse response = request.send();
809        response.disconnect();
810    }
811
812    /**
813     * Creates an upload session to create a new file in chunks.
814     * This will first verify that the file can be created and then open a session for uploading pieces of the file.
815     * @param fileName the name of the file to be created
816     * @param fileSize the size of the file that will be uploaded
817     * @return the created upload session instance
818     */
819    public BoxFileUploadSession.Info createUploadSession(String fileName, long fileSize) {
820
821        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
822        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
823
824        JsonObject body = new JsonObject();
825        body.add("folder_id", this.getID());
826        body.add("file_name", fileName);
827        body.add("file_size", fileSize);
828        request.setBody(body.toString());
829
830        BoxJSONResponse response = (BoxJSONResponse) request.send();
831        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
832
833        String sessionId = jsonObject.get("id").asString();
834        BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId);
835
836        return session.new Info(jsonObject);
837    }
838
839    /**
840     * Creates a new file.
841     * @param inputStream the stream instance that contains the data.
842     * @param fileName the name of the file to be created.
843     * @param fileSize the size of the file that will be uploaded.
844     * @return the created file instance.
845     * @throws InterruptedException when a thread execution is interrupted.
846     * @throws IOException when reading a stream throws exception.
847     */
848    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize)
849        throws InterruptedException, IOException {
850        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
851        return new LargeFileUpload().
852            upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize);
853    }
854
855    /**
856     * Creates a new file using specified number of parallel http connections.
857     * @param inputStream the stream instance that contains the data.
858     * @param fileName the name of the file to be created.
859     * @param fileSize the size of the file that will be uploaded.
860     * @param nParallelConnections number of parallel http connections to use
861     * @param timeOut time to wait before killing the job
862     * @param unit time unit for the time wait value
863     * @return the created file instance.
864     * @throws InterruptedException when a thread execution is interrupted.
865     * @throws IOException when reading a stream throws exception.
866     */
867    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize,
868                                        int nParallelConnections, long timeOut, TimeUnit unit)
869        throws InterruptedException, IOException {
870        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
871        return new LargeFileUpload(nParallelConnections, timeOut, unit).
872            upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize);
873    }
874
875    /**
876     * Contains information about a BoxFolder.
877     */
878    public class Info extends BoxItem.Info {
879        private BoxUploadEmail uploadEmail;
880        private boolean hasCollaborations;
881        private SyncState syncState;
882        private EnumSet<Permission> permissions;
883        private boolean canNonOwnersInvite;
884        private boolean isWatermarked;
885
886        /**
887         * Constructs an empty Info object.
888         */
889        public Info() {
890            super();
891        }
892
893        /**
894         * Constructs an Info object by parsing information from a JSON string.
895         * @param  json the JSON string to parse.
896         */
897        public Info(String json) {
898            super(json);
899        }
900
901        /**
902         * Constructs an Info object using an already parsed JSON object.
903         * @param  jsonObject the parsed JSON object.
904         */
905        Info(JsonObject jsonObject) {
906            super(jsonObject);
907        }
908
909        /**
910         * Gets the upload email for the folder.
911         * @return the upload email for the folder.
912         */
913        public BoxUploadEmail getUploadEmail() {
914            return this.uploadEmail;
915        }
916
917        /**
918         * Sets the upload email for the folder.
919         * @param uploadEmail the upload email for the folder.
920         */
921        public void setUploadEmail(BoxUploadEmail uploadEmail) {
922            if (this.uploadEmail == uploadEmail) {
923                return;
924            }
925
926            this.removeChildObject("folder_upload_email");
927            this.uploadEmail = uploadEmail;
928
929            if (uploadEmail == null) {
930                this.addPendingChange("folder_upload_email", (String) null);
931            } else {
932                this.addChildObject("folder_upload_email", uploadEmail);
933            }
934        }
935
936        /**
937         * Gets whether or not the folder has any collaborations.
938         * @return true if the folder has collaborations; otherwise false.
939         */
940        public boolean getHasCollaborations() {
941            return this.hasCollaborations;
942        }
943
944        /**
945         * Gets the sync state of the folder.
946         * @return the sync state of the folder.
947         */
948        public SyncState getSyncState() {
949            return this.syncState;
950        }
951
952        /**
953         * Sets the sync state of the folder.
954         * @param syncState the sync state of the folder.
955         */
956        public void setSyncState(SyncState syncState) {
957            this.syncState = syncState;
958            this.addPendingChange("sync_state", syncState.toJSONValue());
959        }
960
961        /**
962         * Gets the permissions that the current user has on the folder.
963         * @return the permissions that the current user has on the folder.
964         */
965        public EnumSet<Permission> getPermissions() {
966            return this.permissions;
967        }
968
969        /**
970         * Gets whether or not the non-owners can invite collaborators to the folder.
971         * @return [description]
972         */
973        public boolean getCanNonOwnersInvite() {
974            return this.canNonOwnersInvite;
975        }
976
977        /**
978         * Gets flag indicating whether this file is Watermarked.
979         * @return whether the file is watermarked or not
980         */
981        public boolean getIsWatermarked() {
982            return this.isWatermarked;
983        }
984
985        @Override
986        public BoxFolder getResource() {
987            return BoxFolder.this;
988        }
989
990        @Override
991        protected void parseJSONMember(JsonObject.Member member) {
992            super.parseJSONMember(member);
993
994            String memberName = member.getName();
995            JsonValue value = member.getValue();
996            if (memberName.equals("folder_upload_email")) {
997                if (this.uploadEmail == null) {
998                    this.uploadEmail = new BoxUploadEmail(value.asObject());
999                } else {
1000                    this.uploadEmail.update(value.asObject());
1001                }
1002
1003            } else if (memberName.equals("has_collaborations")) {
1004                this.hasCollaborations = value.asBoolean();
1005
1006            } else if (memberName.equals("sync_state")) {
1007                this.syncState = SyncState.fromJSONValue(value.asString());
1008
1009            } else if (memberName.equals("permissions")) {
1010                this.permissions = this.parsePermissions(value.asObject());
1011
1012            } else if (memberName.equals("can_non_owners_invite")) {
1013                this.canNonOwnersInvite = value.asBoolean();
1014            } else if (memberName.equals("watermark_info")) {
1015                JsonObject jsonObject = value.asObject();
1016                this.isWatermarked = jsonObject.get("is_watermarked").asBoolean();
1017            }
1018        }
1019
1020        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
1021            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
1022            for (JsonObject.Member member : jsonObject) {
1023                JsonValue value = member.getValue();
1024                if (value.isNull() || !value.asBoolean()) {
1025                    continue;
1026                }
1027
1028                String memberName = member.getName();
1029                if (memberName.equals("can_download")) {
1030                    permissions.add(Permission.CAN_DOWNLOAD);
1031                } else if (memberName.equals("can_upload")) {
1032                    permissions.add(Permission.CAN_UPLOAD);
1033                } else if (memberName.equals("can_rename")) {
1034                    permissions.add(Permission.CAN_RENAME);
1035                } else if (memberName.equals("can_delete")) {
1036                    permissions.add(Permission.CAN_DELETE);
1037                } else if (memberName.equals("can_share")) {
1038                    permissions.add(Permission.CAN_SHARE);
1039                } else if (memberName.equals("can_invite_collaborator")) {
1040                    permissions.add(Permission.CAN_INVITE_COLLABORATOR);
1041                } else if (memberName.equals("can_set_share_access")) {
1042                    permissions.add(Permission.CAN_SET_SHARE_ACCESS);
1043                }
1044            }
1045
1046            return permissions;
1047        }
1048    }
1049
1050    /**
1051     * Enumerates the possible sync states that a folder can have.
1052     */
1053    public enum SyncState {
1054        /**
1055         * The folder is synced.
1056         */
1057        SYNCED("synced"),
1058
1059        /**
1060         * The folder is not synced.
1061         */
1062        NOT_SYNCED("not_synced"),
1063
1064        /**
1065         * The folder is partially synced.
1066         */
1067        PARTIALLY_SYNCED("partially_synced");
1068
1069        private final String jsonValue;
1070
1071        private SyncState(String jsonValue) {
1072            this.jsonValue = jsonValue;
1073        }
1074
1075        static SyncState fromJSONValue(String jsonValue) {
1076            return SyncState.valueOf(jsonValue.toUpperCase());
1077        }
1078
1079        String toJSONValue() {
1080            return this.jsonValue;
1081        }
1082    }
1083
1084    /**
1085     * Enumerates the possible permissions that a user can have on a folder.
1086     */
1087    public enum Permission {
1088        /**
1089         * The user can download the folder.
1090         */
1091        CAN_DOWNLOAD("can_download"),
1092
1093        /**
1094         * The user can upload to the folder.
1095         */
1096        CAN_UPLOAD("can_upload"),
1097
1098        /**
1099         * The user can rename the folder.
1100         */
1101        CAN_RENAME("can_rename"),
1102
1103        /**
1104         * The user can delete the folder.
1105         */
1106        CAN_DELETE("can_delete"),
1107
1108        /**
1109         * The user can share the folder.
1110         */
1111        CAN_SHARE("can_share"),
1112
1113        /**
1114         * The user can invite collaborators to the folder.
1115         */
1116        CAN_INVITE_COLLABORATOR("can_invite_collaborator"),
1117
1118        /**
1119         * The user can set the access level for shared links to the folder.
1120         */
1121        CAN_SET_SHARE_ACCESS("can_set_share_access");
1122
1123        private final String jsonValue;
1124
1125        private Permission(String jsonValue) {
1126            this.jsonValue = jsonValue;
1127        }
1128
1129        static Permission fromJSONValue(String jsonValue) {
1130            return Permission.valueOf(jsonValue.toUpperCase());
1131        }
1132
1133        String toJSONValue() {
1134            return this.jsonValue;
1135        }
1136    }
1137}